C语言 将void**类型转换为void* 法律的吗?

btxsgosb  于 2023-03-17  发布在  其他
关注(0)|答案(2)|浏览(217)

我最近在我的测试脚本中实现了类似的东西,当我摆弄标准C时,我对它产生了好奇。这不是家庭作业,只是我自己的一些爱好。

#include <stdio.h>

typedef struct Struct0 {
    int x;
    int y;
} Struct0;

typedef struct Struct1 {
    char x;
    double y;
    char z;
} Struct1;

typedef struct Struct2 {
    double x;
    char y;
    int z;
    char w;
} Struct2;

void my_test_callback(void *pack) {
    // Type cast back to void** 
    void **params = (void **) pack;

    // Unpack data
    Struct0* s0 = (Struct0 *) params[0];
    Struct1* s1 = (Struct1 *) params[1];
    Struct2* s2 = (Struct2 *) params[2];

    // Use the data
    printf("s0: %i %i\n", s0->x, s0->y);
    printf("s1: %c %.2f %c\n", s1->x, s1->y, s1->z);
    printf("s2: %.2f %c %i %c\n", s2->x, s2->y, s2->z, s2->w);
}

int main(int argc, char **argv) {
    // Imagine we have two or more variables that need to be passed
    // to a single function call, as a single parameter.
    Struct0 a;
    Struct1 b;
    Struct2 c;

    // Initializing Struct 0
    a.x = 1;
    a.y = 2;

    // Initializing Struct 1
    b.x = 'a';
    b.y = 3.0;
    b.z = 'b';

    // Initializing Struct 2
    c.x = 4.0;
    c.y = 'c';
    c.z = 5;
    c.w = 'd';

    // So, instead of doing something like this (which is the common way):
    struct packed {
        Struct0 _a;
        Struct1 _b;
        Struct2 _c;
    } pack_data;

    pack_data._a = a;
    pack_data._b = b;
    pack_data._c = c;

    // I would like to do something much more generic, such as:
    void* generic_pack[3];
    generic_pack[0] = (void *) &a; // This way, I don't really need to
    generic_pack[1] = (void *) &b; // know the variables types, and I
    generic_pack[2] = (void *) &c; // can simply type cast them to void*

    // Is it legal to do this? Will it always work?
    my_test_callback((void *) generic_pack);

    // Output from my console:
    // s0: 1 2
    // s1: a 3.00 b
    // s2: 4.00 c 5 d

    return 0;
}

代码编译没有问题,* 在windows 10,gcc.exe(Rev6,由MSYS2项目构建)12.2.0* 上创建和测试,并且它工作时没有任何分段错误。
gen_packvoid **指针)转换为void *类型是否保证每次运行脚本时都能正常工作?或者我们只是幸运地没有发生分段错误?

mcvgt66p

mcvgt66p1#

您所做的是有效的。* 任何 * 对象指针类型都可以自由地与void *转换,其中包括void **
你从一个void *数组开始,每一个数组都被赋以不同的结构体指针值,这对上面的转换规则是有效的,然后你把你的void *数组,它衰减为void **,并把它传递给一个期望void *的函数,这也是有效的。
在函数内部,将void *参数转换为void **,这对于上述规则是有效的,并且返回了开始时的类型,然后迭代void *数组,并将每个数组赋给不同的struct类型,这对于上述规则也是有效的,并且返回了开始时的指针类型。

pb3skfrl

pb3skfrl2#

转换本身对于C来说是可以的。C允许几乎任何形式的野生和疯狂的指针转换--特别是在void*的情况下,它们可以在没有强制转换的情况下执行。只有当你通过错误的类型解除引用指针时,问题才开始出现。

  • generic_pack[0] = (void *) &a;这是确定的,你甚至不需要演员阵容。
  • my_test_callback((void *) generic_pack);这是可疑的,但是强制类型转换本身是有效的。这里所做的是将一个“decayed”数组指针(从void* [3]void**)转换为void*。同样,转换为void*时不需要强制类型转换。
  • void **params = (void **) pack;由于调用者代码有数组衰减,这是可以的。但是我们不需要强制类型转换。

如果调用者代码是(void *) &generic_pack,那么它将是错误的和有问题的。
类似地,如果您将参数声明为void* pack[3],那么该参数将“衰减”为void**,也不会出现任何问题。

  • 取消引用一个void**并在其上执行指针运算。它指向一个void*数组,所以这是可以的。但是同样,不需要强制转换。

除此之外,可以将void* [3]数组的成员强制转换为结构指针,因为这是您最初存储在那里的内容。
请注意,如果您决定使用void**指向struct数组,则不将void*数组声明为中间步骤会产生更多问题。因为 * 只有 * void*是特殊的泛型指针类型,所以它不适用于void**
总而言之,void*是不安全和危险的,所以明智的做法是避免它们。实现代码的正确方法应该是创建第四个struct,其中包含另外三个:

typedef struct
{
  Struct0 s0;
  Struct1 s1;
  Struct2 s2;
} everything;

void my_test_callback(const everything* pack)

或者,该结构体可能包含了指向其他位置的结构体的指针,到目前为止,最好的选择当然是重写函数以接受3个参数。

相关问题