gpt4 book ai didi

c - 通过来自其他结构成员的偏移指针访问结构成员是否合法?

转载 作者:太空狗 更新时间:2023-10-29 17:17:42 24 4
gpt4 key购买 nike

在这两个示例中,通过偏移其他成员的指针来访问结构成员是否会导致未定义/未指定/实现定义的行为?

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 段似乎表明这应该由实现定义:

Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.

然后继续说:

There may be unnamed padding within a structure object, but not at its beginning.

但是,像下面这样的代码似乎相当普遍:

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 相同,并且以一种方式引用它是合法且没有意义的另一个不是,但同样地,将外部 union 与数组添加应该突然使 (&foo3.a)[1] 合法是没有意义的。

因此,我思考第一个例子的推理也必须是合法的:

  1. foo3.arr 保证与 &foo.a
  2. 相同
  3. foo3.arr + 1&foo3.b 指向相同的内存位置
  4. &foo3.a + 1&foo3.b 因此必须指向相同的内存位置(从 1 到 2)
  5. 结构布局需要保持一致,所以&foo1.a&foo1.b的布局应该和&foo3.a完全一样> 和 &foo3.b
  6. &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!",但是这种行为是否由标准保证,或者它是未定义的还是实现定义的行为?

最佳答案

简介:这方面的标准是不充分的,关于这个话题和严格的别名有几十年的争论历史,没有令人信服的解决方案或建议来解决。

这个答案反射(reflect)了我的观点,而不是标准的强加。


首先:人们普遍认为,您的第一个代码示例中的代码是未定义行为,因为通过直接指针算法访问数组边界之外。

规则是 C11 6.5.6/8 。它说指针的索引必须保留在“数组对象”(或末尾的一个对象)内。它没有说明 哪个 数组对象,但通常认为在 int *p = &foo.a; 的情况下,“数组对象”是 foo .a,而不是任何以 foo.a 为子对象的更大对象。

相关链接: one , two .


其次:人们普遍认为您的两个 union 示例都是正确的。该标准明确表示可以读取 union 体的任何成员;并且相关内存位置的任何内容都被解释为正在读取的 union 成员的类型。


您建议 union 正确意味着第一个代码也应该正确,但事实并非如此。问题不在于指定读取的内存位置;问题在于我们如何得出指定该内存位置的表达式。

即使我们知道 &foo.a + 1&foo.b 是相同的内存地址,访问 int 也是有效的通过第二个并且通过第一个访问 int 无效。

人们普遍认为,您可以通过以其他不违反 6.5.6/8 规则的方式计算其地址来访问 int,例如:

((int *)((char *)&foo + offsetof(foo, b))[0]

((int *)((uintptr_t)&foo.a + sizeof(int)))[0]

相关链接:one , two


关于 ((int *)&foo)[1] 是否有效,普遍达成共识。有人说它与您的第一个代码基本相同,因为标准说“指向对象的指针,经过适当转换,指向元素的第一个对象”。其他人说它与我上面的 (char *) 示例基本相同,因为它遵循指针转换规范。一些人甚至声称这是一个严格的别名违规行为,因为它将结构别名为数组。

可能相关的是 N2090 - Pointer provenance proposal .这并不直接解决问题,也不建议废除 6.5.6/8。

关于c - 通过来自其他结构成员的偏移指针访问结构成员是否合法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51737910/

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