C - 非标准结构"兼容性"

时间:2015-01-08 08:02:20

标签: c struct casting undefined-behavior

简而言之,我的问题是:

C标准明确规定struct成员应具有按其声明顺序增长的相对地址。它也没有说明结构成员应该如何对齐的任何细节。显然,这是为了允许padded和packed-struct实现。但是,从理论上讲,可以有一个符合标准的编译器,只要它们以与声明成员相同的顺序增长,就会给结构成员提供完全随机的地址。但这样的编译器是否存在?

这里有一些细节。考虑以下两个结构:

struct s1 {
    int var1;
    char var2;
    long var3;
};

struct s2 {
    int var1;
    char var2;
    long var3;
    char var4;
    int var5;
};

和以下代码:

printf("offsetof(struct s1, var2) = %d\n", 
        offsetof(struct s1, var2));

printf("offsetof(struct s2, var2) = %d\n", 
        offsetof(struct s2, var2));

printf("offsetof(struct s1, var3) = %d\n", 
        offsetof(struct s1, var3));

printf("offsetof(struct s2, var3) = %d\n", 
        offsetof(struct s2, var3));

gcc(GCC)4.8.3 20140911 产生以下输出:

offsetof(struct s1, var2) = 4                                                 
offsetof(struct s2, var2) = 4                                                 
offsetof(struct s1, var3) = 8                                                 
offsetof(struct s2, var3) = 8

这非常有意义:一个常规的符合标准的编译器(不对结构成员重新排序的编译器),在对结构成员执行填充时,只考虑前一个struct成员的大小和偏移量。这意味着具有相应类型的两个结构的第一个成员的相对地址在这些编译器上将始终相同。反过来,这意味着在我们的示例中,我们可以安全地执行以下操作:

struct s2 test_s2, *ptest_s2;
struct s1 test_s1, *ptest_s1;

ptest_s2 = &test_s2;
ptest_s1 = &test_s1;

ptest_s2->var1 = 1;
ptest_s2->var2 = '2';

ptest_s1 = (struct s1*)ptest_s2;

printf("ptest_s1->var1 = %d\n", ptest_s1->var1);
printf("ptest_s1->var2 = %c\n", ptest_s1->var2);

编译并运行良好,并在同一编译器上提供输出

ptest_s1->var1 = 1                                                            
ptest_s1->var2 = 2

由于所有指向结构的指针都具有相同的表示和标准对齐,因此这里UB的唯一来源实际上是期望具有相应类型的第一个结构成员的相对地址在两个结构中是相同的。 / p>

现在,这里有一个实际的问题:是否存在任何真实编译器(那些不重新排序结构成员的编译器),其中相对地址可能不同?

PS 我知道在C11中,我可以通过一个匿名的实例替换第二个结构中的第一个结构成员,以一种定义明确的方式获得完全相同的结果第一个结构(顺便说一句,据我所知,它应该在内部以相同的方式工作),但我想编写可以在不支持匿名结构的编译器版本上执行相同操作的代码。

2 个答案:

答案 0 :(得分:2)

这个问题比你想象的要多。 据我所知,答案是合格的' no'

协商一致似乎是编制者没有真正的理由去填充成员,除了确保他们与他们的开始正确对齐并且可以占据阵列中的连续位置。

标准要求第一个成员位于struct

的开头

我只能找到人们(在这里,网络等),他们认为以下是确定T型对齐的最便携的已知方式,并且没有人提供过不兼容的平台。

#include<stddef.h>

#define alignment(T) (offsetof(struct {char w;T v;},v))

编译器开发人员不会因为没有充分理由而浪费内存。然而,理论上可能(例如)某人可能决定将未对齐的成员放置在填充区域的末尾而不是起始区域。 甚至可以想象调试编译器可以添加&#39;覆盖哨兵&#39;在数组类型的末尾。

但是我找不到编译器(或声称)编译器(当没有打包数据时)除了从第一个成员开始做任何事情之外,为最后一个成员填充最小值,然后为最严格的对齐成员填充。 / p>

然而,即使在单一架构上,不同的编译器也可能对原始类型做出不同的决定,因此即使在相同的硬件架构上,一个struct也可能具有不同的布局。

因此,您不能依赖于此来实现互操作性。

答案 1 :(得分:1)

struct s3 {
    int var1;
    int var2;
    int var3;
};

struct s4 {
    int var1;
    int var2;
    int var3;
    long long var4;
};

添加具有更强对齐要求的类型时,则更改整个结构的对齐方式。

然后当您对指针进行转换和取消引用时,它就是UB。

在上面的代码中,我认为最后添加var4会将var1从字对齐更改为双字对齐,假设int是字对齐的并且{{ 1}}是双字对齐的。

long long是一个非常糟糕的例子,因为它在32位gcc中是32位,在64位gcc中是64位。