下标操作符通过加法操作符定义。在这个相同的表达式中,数组衰减为第一个元素的指针,因此指针算术规则适用。间接操作符用于加法的假设结果。
“[expr.sub]”:方括号内后跟表达式的后缀表达式是一种后缀表达式。其中一个表达式应为类型为“T”数组的glvalue或类型为“T”指针的prvalue,另一个应为未作用域枚举类型或整型的prvalue。结果的类型为“T”。类型“T”应为完全定义的对象类型。表达式E1[E2]与*((E1)+(E2))(根据定义)相同,...
如果数组索引超过最后一个元素即E2> std::size(E1),则假设的指针算术本身是未定义的。
“[expr.add]”:当将具有整数类型的表达式J添加到或从指针类型的表达式P时,结果具有P的类型。
• 如果P计算为空指针值...(不适用)
• 否则,如果P指向具有n个元素的数组对象x的数组元素i,则表达式P+J和J+P(其中J具有值j)指向x的(可能虚构的)数组元素i+j,如果0≤i+j≤n,并且表达式P-J指向x的(可能虚构的)数组元素i−j,如果0≤i−j≤n。(当i-j> n时不适用)
• 否则,行为未定义。
在E2 == std::size(E1)的情况下(这是示例的最后一次迭代),加法的假设结果是指向数组之外的一个元素的指针。虚拟的指针算术是定义良好的。
“[basic.compound]”:指向一个对象结束后的指针类型值表示对象占用的存储空间结束后的内存中的第一个字节。
访问是根据对象定义的。但是那里没有对象,甚至没有存储空间,因此不存在行为的定义。
好吧,在某些情况下,该指针可能与指向指定地址的其他无关对象相关联。下面的注释说明了超出末尾的指针不是指向共享地址的这种不相关对象的指针。我找不到哪个规范规则导致了这一点。
[注2:一个对象之后的指针([expr.add])不被认为指向该地址上的无关对象,即使这个无关对象是同一类型的。 ...
或者我们可以看一下间接操作符的定义:
[expr.unary.op]
一元*操作符执行间接引用:应用它的表达式应该是指向对象类型的指针...结果是一个左值,指向表达式所指向的对象...。...
存在矛盾,因为没有可以引用的对象。
因此,总之:
int table[N] = {};
table[N] == 0; // UB, accessing non-existing object
table[N + 1]; // UB, [expr.add]
table + N; // OK, one past last element
table[N]; // ¯\_(ツ)_/¯ See CWG 232
table+4
是一个有效的指针。它的间接调用会引发UB。 - YSC0
到N-1
的元素存在于连续空间中。任何超出此范围的内容都将是不可访问的对象。 - Human-Compiler