gpt4 book ai didi

C - 非标准结构 "compatibility"

转载 作者:太空宇宙 更新时间:2023-11-04 02:00:28 28 4
gpt4 key购买 nike

简而言之,我的问题是:

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

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

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 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.S. 我知道在 C11 中,我可以通过将第二个结构中的第一个结构的成员替换为第一个结构的匿名实例(其中,顺便说一句,据我所知,内部应该以相同的方式工作),但我想编写可以在不支持匿名结构的编译器版本上执行相同操作的代码。

最佳答案

这个问题出现的次数比您想象的要多。据我所知,答案是有条件的“否”。

共识似乎是编译器没有真正的理由填充成员,除了确保它们与它们的开始正确对齐并且可以占据数组中的连续位置。

标准要求第一个成员位于struct 的开头。

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

#include<stddef.h>

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

编译器开发人员不会无缘无故地浪费内存。然而,从理论上讲,(比如说)有人可能决定将未对齐的成员放置在填充区域的末尾而不是开始处。甚至可以想象调试编译器可以在数组类型的末尾添加“覆盖标记”。

但是我找不到一个编译器的样本(或声明)(当不打包数据时)除了从第一个成员开始之外做任何事情,为下一个成员填充最少,然后为最严格对齐的成员结束填充。

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

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

关于C - 非标准结构 "compatibility",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27835472/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com