【发布时间】:2019-01-15 04:49:09
【问题描述】:
在这两个示例中,通过偏移其他成员的指针来访问结构的成员会导致未定义/未指定/实现定义的行为吗?
struct {
int a;
int b;
} foo1 = {0, 0};
(&foo1.a)[1] = 1;
printf("%d", foo1.b);
struct {
int arr[1];
int b;
} foo2 = {{0}, 0};
foo2.arr[1] = 1;
printf("%d", foo2.b);
C11 § 6.7.2.1 的第 14 段似乎表明这应该是实现定义的:
结构或联合对象的每个非位域成员都以适合其类型的实现定义的方式对齐。
然后接着说:
结构对象中可能有未命名的填充,但不是在其开头。
但是,如下代码似乎相当普遍:
union {
int arr[2];
struct {
int a;
int b;
};
} foo3 = {{0, 0}};
foo3.arr[1] = 1;
printf("%d", foo3.b);
(&foo3.a)[1] = 2; // appears to be illegal despite foo3.arr == &foo3.a
printf("%d", foo3.b);
该标准似乎保证 foo3.arr 与 &foo3.a 相同,并且以一种方式引用它是合法的,而另一种方式则不合法是没有意义的,但同样地,添加与数组的外部联合应该突然使(&foo3.a)[1] 合法。
因此,我认为第一个示例的推理也必须是合法的:
-
foo3.arr保证与&foo.a相同 -
foo3.arr + 1和&foo3.b指向同一个内存位置 -
因此,
&foo3.a + 1和&foo3.b必须指向相同的内存位置(从 1 和 2) - 结构布局需要一致,所以
&foo1.a和&foo1.b的布局应该和&foo3.a和&foo3.b完全一样 -
因此,
&foo1.a + 1和&foo1.b必须指向相同的内存位置(从 3 和 4)
我遇到了一些外部消息来源,表明 foo3.arr[1] 和 (&foo3.a)[1] 示例都是非法的,但是我无法在标准中找到可以做到这一点的具体声明。
即使它们都是非法的,也可以使用灵活的数组指针构造相同的场景,据我所知,确实具有标准定义的行为。
union {
struct {
int x;
int arr[];
};
struct {
int y;
int a;
int b;
};
} foo4;
原始应用程序正在考虑从一个结构字段到另一个结构字段的缓冲区溢出是否严格按照标准定义:
struct {
char buffer[8];
char overflow[8];
} buf;
strcpy(buf.buffer, "Hello world!");
println(buf.overflow);
我希望这会在几乎所有现实世界的编译器上输出"rld!",但这种行为是由标准保证的,还是未定义或实现定义的行为?
【问题讨论】:
-
@M.M 第二部分的原因是联合代码的假定有效性似乎暗示第一个样本也应该有效。不过,我想将另一个仅询问联合代码有效性的问题分开可能是有意义的。
-
@AJMansfield 标准中没有这样的含义;工会有特殊规定
-
foo.arr[1] = 1;是 UB,没有规定下一个成员是foo.arr[1]。 -
@chux 可以用
assert解决异议;或者注意写入填充字节是合法的 -
@M.M Legal 写入填充。嗯。我怀疑将任何位模式写入填充是可以的。有时这是隐藏检查位的地方。也许是个好问题。
标签: c pointers struct language-lawyer c11