c++ - C++ 中指针运算的 a+i 和 &a[i] 有什么区别?

标签 c++ language-lawyer pointer-arithmetic

假设我们有:

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=nulli=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=nulli=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/

相关文章:

c++ - 在成员初始化程序中创建临时对象时销毁它的点

c++ - 复制构造函数是始终隐式定义,还是仅在使用时定义?

c++ - boolean 复合赋值中的隐式转换?

c++ - MATLAB libpointer 数组

c++ - C++中的并发哈希表

c - C 中的指针算术

c++ - 指针运算和继承导致未定义的行为

c - 指针算法得到错误的输出

c++ - 带 NULL 的 Strtok 用法

c++ - 可以将currying与lambda函数一起使用吗?