假设我们有:
char* a;
int i;
许多 C++ 介绍(如 this one)表明右值 a+i
和 &a[i]
是可以互换的。几十年来我一直天真地相信这一点,直到最近我偶然发现了以下引用自 here 的文字 ( [dcl.ref] ) :
in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior.
换句话说,将引用对象“绑定(bind)”到空解除引用会导致未定义的行为。基于context of the above text ,人们推断仅仅评估 &a[i]
(在offsetof
宏中)被认为是“绑定(bind)”一个引用。此外,似乎一致认为 &a[i]
在 a=null
和 i=0
的情况下会导致未定义的行为。此行为不同于 a+i
(至少 in C++, in the a=null, i=0 case )。
这导致至少有 2 个关于 a+i
和 &a[i]
之间的区别的问题:
首先,a+i
和 &a[i]
之间的底层语义差异是什么导致了这种行为差异。是否可以根据任何类型的一般原则来解释它,而不仅仅是“将引用绑定(bind)到空取消引用对象会导致未定义的行为,因为这是每个人都知道的非常特殊的情况”? &a[i]
是否可能生成对a[i]
的内存访问?或者规范作者那天对空引用不满意?还是别的?
其次,除了a=null
和i=0
的情况,还有没有其他情况a+i
和&a[i]
行为不同? (可以包含在第一个问题中,具体取决于对它的回答。)
最佳答案
TL;DR:a+i
和 &a[i]
都是良构的,当 a
是 a根据标准(的意图),空指针和 i
为 0,并且所有编译器都同意。
a+i
根据 [expr.add]/4 显然是合式的最新标准草案:
When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.
- If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
- [...]
&a[i]
很棘手。每[expr.sub]/1 , a[i]
等价于*(a+i)
, 因此&a[i]
等价于&*( a+i)
。现在标准还不太清楚当a+i
为空指针时&*(a+i)
是否格式正确。但是作为@n.m。在 comment 中指出,记录在 cwg 232 中的意图就是允许这种情况。
由于核心语言UB需要被常量表达式([expr.const]/(4.6))捕获,我们可以测试编译器是否认为这两个表达式是UB。
这是演示,如果编译器认为 static_assert
中的常量表达式是 UB,或者如果他们认为结果不是 true
,那么他们必须产生一个诊断 (错误或警告)按照标准:
(请注意,这使用单参数 static_assert 和 constexpr lambda,它们是 C++17 的特性,以及默认的 lambda 参数,这也是很新的)
static_assert(nullptr == [](char* a=nullptr, int i=0) {
return a+i;
}());
static_assert(nullptr == [](char* a=nullptr, int i=0) {
return &a[i];
}());
来自 https://godbolt.org/z/hhsV4I ,似乎所有编译器在这种情况下都表现一致,根本不产生任何诊断(这让我有点惊讶)。
但是,这与 offset
的情况不同。 that question 中发布的实现显式创建引用(这是回避用户定义的 operator&
所必需的),因此受引用要求的约束。
关于c++ - C++ 中指针运算的 a+i 和 &a[i] 有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54938610/