如果其他成员跟在结构成员的末尾,则写入/读取该末尾

Write/read past the end of structure member if another member follows it

本文关键字:成员 读取 结构 如果 其他      更新时间:2023-10-16

假设我有一个结构:

struct Foo{
char a[4];
char b[4];
//...some other members
} foo, foo2;

让我们假设ab之间没有填充,所以offsetof(Foo, a) == 0offsetof(Foo, b) == 4,并且Foo是POD。

标准如何定义以下内容?

putc(foo.a[4]); 
// Am I allowed to read past the `a` array?
foo.a[4] = 0;
// What is (foo.b[0]) ?
memcpy(foo.a, "ABCDEFGH", 8);
// Is foo.b equals "EFGH"?
memcpy(foo2.a, foo.a, 8);
// Is foo2.b equals foo.b?

在我的理解中,如果我从以下法律表达开始:

*((char*)&foo + offsetof(Foo, b))

我可以应用一系列替换:

*((char*)&foo + 4)
*((char*)&foo + 0 + 4)
*((char*)&foo + offsetof(Foo, a) + 4)
*(foo.a + 0 + 4)
*(foo.a + 4)
foo.a[4]

另外两个是合法的,因为foo.a等价于(char*)&foo,如果我们足够小心的话,我们可以memcpy的一部分结构。

不允许读取或写入foo.a[4]

如[dcl.array]/6中所述,

类型为"的对象;CCD_ 10CCD_;由类型为UN子对象的连续分配的非空集合组成,称为阵列的元素,编号为0N-1

foo.a中没有元素4。

现在,【basic.compound】/3确实声明:

[…]为了进行指针算术(7.6.6)和比较(7.6.9、7.6.10)n元素的数组x的最后一个元素被认为等价于指向假设数组的指针x的元素n和不是数组元素的T类型的对象被认为属于数组具有一个类型为CCD_ 20的元件。[…]

这使得表达式foo.a + 4定义良好;它是指向";假设的";索引为4的元素。然而,没有实际的这样的对象,因此没有什么可读取或写入的。

你可能会反对";在那里有一个对象:CCD_ 22";。然而,这种解释与〔basic.compound〕/3:中给出的指针值分类法相矛盾

〔…〕指针类型的每个值都是以下值之一:

  • 指向对象或函数的指针(据说该指针指向指向对象或函数),或
  • 一个指针经过对象的末尾(7.6.6),或
  • 该类型的空指针值,或者
  • 无效的指针值

[…]

因为指针foo.a + 4是经过foo.a末尾的指针,所以它也不能是指向foo.b[0]的指针,即使它们表示的地址值可能相同(假设ab之间没有填充)。它只能分为四类中的一类。

对于将一元*运算符应用于过去的结束指针值是否实际合法,该标准在措辞上存在差距。[expr.unary.op]/1表示一元CCD_;产生表示操作数所指向的对象或函数的CCD_ 30类型的左值"如果没有这样的对象,则不清楚行为是否定义明确;你得到一个";"lvalue through the end";,还是你得了UB?然而,所有已知的实现都将其视为不是UB(它们允许在常量表达式求值期间使用它)。只有当你尝试阅读或写作时,行为才是不明确的。