介绍:该领域的标准是不够的,并且关于该主题和严格别名的争论已有数十年的历史,没有令人信服的解决方案或建议来解决。
这个答案反映了我的观点,而不是该标准的任何强加。
首先:人们普遍认为第一个代码示例中的代码是未定义的行为,因为通过直接指针算术访问数组的边界之外。
规则是 C11 6.5.6/8 。它表示指针的索引必须保留在“数组对象”内(或超出末尾的一个)。它没有说which数组对象,但普遍认为在这种情况下int *p = &foo.a;
那么“数组对象”是foo.a
,而不是任何更大的物体foo.a
是一个子对象。
相关链接:one https://stackoverflow.com/questions/47224138/is-it-ok-to-access-past-the-size-of-a-structure-via-member-address-with-enough/47224596#47224596, two https://stackoverflow.com/questions/48147422/is-it-legal-to-alias-a-struct-and-an-array.
其次:人们普遍认为你们俩union
例子是正确的。该标准明确规定联盟的任何成员都可以被读取;无论相关内存位置的内容是什么,都将被解释为正在读取的联合成员的类型。
你建议union
正确意味着第一个代码也应该是正确的,但事实并非如此。问题不在于指定读取的内存位置;而在于指定读取的内存位置。问题在于我们如何得出指定该内存位置的表达式。
尽管我们知道&foo.a + 1
and &foo.b
是相同的内存地址,访问是有效的int
通过第二个并且无法有效访问int
通过第一个。
人们普遍认为,您可以通过不违反 6.5.6/8 规则的其他方式计算其地址来访问 int,例如:
((int *)((char *)&foo + offsetof(foo, b))[0]
or
((int *)((uintptr_t)&foo.a + sizeof(int)))[0]
相关链接:one https://stackoverflow.com/questions/5524552/, two https://stackoverflow.com/questions/37412887/
It's not普遍同意是否((int *)&foo)[1]
已验证。有人说它与您的第一个代码基本相同,因为标准说“指向对象的指针,经过适当转换,指向元素的第一个对象”。别人说和我的基本一样(char *)
上面的例子是因为它遵循指针转换的规范。有些人甚至声称这是严格的别名违规,因为它将结构别名为数组。
也许相关的是N2090 - 指针来源提案 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2090.htm。这并不直接解决该问题,也不建议废除 6.5.6/8。