在C++中,指针算法的a + i和&a [i]有什么区别?

per*_*oud 9 c++ pointer-arithmetic language-lawyer

假设我们有:

char* a;
int   i;
Run Code Online (Sandbox Code Playgroud)

许多引入到C++(像这样一个)表明,右值a+i&a[i]可以互换.我天真地相信这几十年,直到我最近偶然发现了[dcl.ref]引用的以下文字(这里):

特别是,空引用不能存在于定义良好的程序中,因为创建这样的引用的唯一方法是将它绑定到通过解引用空指针获得的"对象",这会导致未定义的行为.

换句话说,将引用对象"绑定"到空取消引用会导致未定义的行为.基于上述文本上下文,可以推断仅仅评估 &a[i](在offsetof宏内)被认为是"约束"参考.此外,似乎存在共识,&a[i]导致在a=null和的情况下出现未定义的行为i=0.这种行为不同于a+i(至少在C++中,在a = null,i = 0的情况下).

这导致约之间的差异至少2个问题a+i&a[i]:

首先,是什么底层之间的语义差别a+i以及&a[i]导致这种行为差异.是否可以根据任何类型的一般原则来解释,而不仅仅是"绑定对空取消引用对象的引用会导致未定义的行为,因为这是一个每个人都知道的特定情况"?是否&a[i]可以生成内存访问a[i]?或者规范作者对那天的空引用不满意?或者是其他东西?

其次,除了情况a=nulli=0,是否有任何其他地方的情况a+i&a[i]不同的表现?(第一个问题可以涵盖,取决于答案.)

cpp*_*ner 7

TL; DR:a+i并且&a[i]都是格式良好的并且当a是空指针时产生空指针并且i根据标准的(意图)为0,并且所有编译器都同意.


a+i根据最新标准草案的[expr.add]/4显然是格式良好的:

当具有整数类型的表达式J被添加到指针类型的表达式P或从其中减去时,结果具有类型P.

  • 如果P求值为空指针值且J求值为0,则结果为空指针值.
  • [...]

&a[i]很棘手 Per [expr.sub]/1,a[i]相当于*(a+i),因此&a[i]相当于&*(a+i).现在,&*(a+i)a+i空指针是否格式良好时,标准还不是很清楚.但正如@nm在评论中指出的那样,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];
}());
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/hhsV4I看,似乎所有编译器在这种情况下表现均匀,根本不产生任何诊断(这让我感到惊讶).


但是,这与offset案例不同.在该问题中发布的实现明确地创建了一个引用(这是回避用户定义所必需的operator&),因此受引用要求的约束.


Chr*_*phe 6

在C ++标准的[expr.sub] / 1部分中,您可以阅读:

该表达式E1[E2]与相同(根据定义)*((E1)+(E2))

这意味着与&a[i]完全相同&*(a+i)。因此,您将*首先取消引用指针,然后再获取地址&。如果指针无效(即 nullptr,但也超出范围),则为UB。

a+i基于指针算法。乍一看,它看起来不太危险,因为肯定没有UB被取消引用。但是,它也可能是UB(请参阅[expr.add] / 4

当将具有整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果表达式P指向具有n个元素的数组对象x的元素x [i],则表达式P + J和J + P(其中J的值为j)指向(可能是假想的)元素x [i + j]如果为0?我+ j?n; 否则,行为是undefined。同样,表达式P-J指向(可能是假设的)元素x [i?j]如果为0?一世 ??n; 否则,行为是不确定的。

因此,尽管这两个表达式的语义略有不同,但我想说的是最后的结果是相同的。