我在哪里可以使用struct兼容性?

时间:2014-08-03 08:43:29

标签: c types struct enums

我正在this页面上阅读C中的类型推广,并发现了更多关于struct/union/enum的内容:

  

6.12.2单独的编译兼容性

     

由于每个编译可能会查看不同的源文件,大多数   不同编译中兼容类型的规则是   结构性质:

     

必须匹配标量(整数,浮点和指针)类型   兼容,就好像它们在同一个源文件中一样。

     

匹配结构,联合和枚举必须具有相同的数量   成员。每个匹配成员必须具有兼容类型(在   单独的编译意义),包括位域宽度。

     

匹配结构必须具有相同顺序的成员。命令   联盟和枚举成员并不重要。

     

匹配的枚举成员必须具有相同的值。

     

另一个要求是成员的名字,包括   缺乏未命名成员的名字,结构,工会和工会的匹配   枚举,但不一定是各自的标签。

我的问题:在哪种意义上结构“等于”?在哪里以及为什么可以使用它?有人可以给出一个代码示例吗?

显然,如果没有强制转换,下面的代码不会编译而没有警告。由于C不是强类型语言,因此它可以与任何结构一起使用。所以我真的没有看到它可能有用的地方;

struct foo_t
{
    int a;
    int b;
};

struct baa_t
{
    int a;
    int b;
};

void print(struct foo_t*);

int main(void)
{
    struct foo_t a = {1,2};
    struct baa_t b = {3,4};

    print(&a);
    print(&b); //cast needed
    return 0;   
}


void print(struct foo_t *f)
{
    printf("a = %d\r\n", f->a);
    printf("b = %d\r\n", f->b);
}

2 个答案:

答案 0 :(得分:3)

这主要涉及让多个.cpp文件引用相同的头文件并使用它的结构(或者更糟糕的是,两者都独立定义它)。

每个.cpp文件将在一个完全独立的独立进程中编译,并且既不知道另一个(忽略LTCG),但它们都将以相同的方式处理结构,因此在一个.cpp文件中使用的结构将具有与另一个完全相同的布局。

同样适用于结构中的结构 - 一个.cpp文件可能有一个包含另一个结构的结构,另一个.cpp可能只暴露给指向内部结构的指针而不知道它周围的内容。但是,另一个.cpp需要能够对它进行操作,因此数据成员的实际布局需要完全匹配。

答案 1 :(得分:1)

C语言是在目标文件链接系统非常原始的时候创建的;不幸的是,在许多情况下,他们仍然是。

假设以下.C文件

struct sheep1 {int16_t a, b;}
struct sheep2 {int16_t a; unsigned char b[2];}
int foo1(struct sheep1 bah) { return a+(b & 255)+(b >> 8); }
int foo2(struct sheep2 bah) { return a+b[0]+b[1]; }
int foo2(struct sheep2 bah) { return a+b[0]+b[1]; }

从另一个.C文件中调用

struct sheep1 {int16_t a, b;}
struct sheep2 {int16_t a; unsigned char b[2];}
extern int foo1(struct1 sheep bah);
extern int foo2(struct2 sheep bah);
extern int foo3(struct1 sheep bah);
...
struct sheep1 marysLamb;
struct sheep2 marysLamb;
...
foo1(marysLamb1);
foo2(marysLamb2);
foo3(marysLamb1);

请注意,sheep1sheep2都是四个字节长,并且两者都具有相同的对齐要求。尽管如此,对foo3的呼吁可能会以惊人的方式失败。

foo1生成的代码可能希望调用者将bah的字段放入两个CPU寄存器中,因为这比将它们放入内存要快。当调用代码调用foo1时,它会按预期将值放入寄存器中,一切都会正常工作。

因为结构sheep2包含一个数组,所以调用约定可能不允许它在寄存器中传递(即使它足够小,可以适合,编译器通常不具备处理单个字节的能力)在寄存器内)。因此,调用者需要将其放入堆栈中的四个字节的物理内存中。当代码调用该方法时需要这样(调用foo2的情况),调用者将在堆栈上放置四个字节,被调用的方法将使用该字节,并且(在某些环境中)删除。尽管foo2必须以foo1不同的方式调用,但编译器会知道这一点并相应地生成代码。

然而,随着foo3的调用,事情可能会失败。编译器会将marysLamb1的字段加载到寄存器中并调用foo3。但是,该函数将期望调用者在堆栈上推送了四个字节的数据。因此,它将从堆栈中读取四个字节的who-know-what,对它们执行指示的算术。然后,在某些环境中,它可能会在返回时从堆栈中弹出这四个字节。如果调用者在这四个字节的堆栈中有重要的东西(通常就是这种情况),那么就不知道会发生什么。

在最初设计C的系统上,链接器对方法foo1,foo2和foo3一无所知,除了它们的名称和放置代码的位置。但是,为了避免上述问题,一些较新的系统定义了编译器可以提供有关参数方法期望来自调用者的信息或调用者将要提供的参数的更多信息的方法。然后,如果呼叫者无法提供正确的信息,联系人可以利用这些信息来提供警告或错误。 C标准的维护者不希望阻止这样的努力,但是他们希望避免链接器在代码中发出声音的情况 - 但是对于这样的嘎嘎声 - 工作得很好。在存在潜在差异的情况下,标准有必要指定允许连接器“挑剔”的情况。以及即使它们看起来很可疑也会被接受的案例#34;