【发布时间】:2015-03-06 07:01:55
【问题描述】:
简而言之,我的问题是:
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 的唯一来源实际上是期望具有相应类型的第一个结构成员的相对地址在两个结构中相同。
现在,真正的问题来了:现实世界的编译器(那些不重新排序结构成员的编译器)的相对地址可以不同吗?
PS 我知道在 C11 中,通过将第二个结构中的第一个结构的成员替换为第一个结构的匿名实例(其中,顺便说一下,据我了解,内部应该以相同的方式工作),但我想编写可以在不支持匿名结构的编译器版本上执行相同操作的代码。
【问题讨论】:
-
我会说'关于主题'-'这样的编译器是否存在'相当于'这个逻辑是否可移植'。
-
今天不存在的东西明天可能会存在,并在下周成为主流。你打算每天问这个问题吗?坚持标准更容易。
-
@Dan Allen:比题外话,如要求一个非现场工具,编译器只是什么。
-
@zaibis 我会说“是以下便携式设备”几乎总是热门话题。
-
@LuisColorado:它是一个宏,自 C89 以来一直是标准的一部分,它在 stddef.h (en.wikipedia.org/wiki/Offsetof) 中定义
标签: c struct casting undefined-behavior