超过最后一个数组元素末尾的指针是否等于超过整个数组末尾的指针?

Lan*_*yer 5 c++ arrays pointers language-lawyer

编译器在编译代码时出现分歧

int main()
{
    constexpr int arr[3] = {};
    static_assert((void*)(arr + 3) == (void*)(&arr + 1));
}
Run Code Online (Sandbox Code Playgroud)

在 GCC 和 Clang 中,static_assert不会触发,MSVC 认为static_assert失败https://godbolt.org/z/dHgmEN

(void*)(arr + 3) == (void*)(&arr + 1)评估为true,为什么?

eer*_*ika 3

最新标准草案相关规则:

\n\n
\n

[介绍对象]

\n\n

对象可以包含其他对象,称为子对象。\n 子对象可以是成员子对象 ([class.mem])、基类子对象 ([class.衍生]) 或数组元素。\n 不是一个对象任何其他对象的子对象称为完整对象。

\n
\n\n

因此,数组元素是子对象。示例中的数组是一个完整的对象。

\n\n
\n\n
\n

[表达式.eq]

\n\n

...比较指针定义如下:

\n\n
    \n
  • 如果一个指针代表一个完整对象的地址,而另一个指针代表另一个完整对象的最后一个元素之后的地址,79比较的结果是未指定的。

  • \n
  • 否则,如果...两者都代表相同的地址,则它们比较相等。

  • \n
\n\n

79)正如 [basic.compound] 中所指定的,为此目的,不是数组元素的对象被视为属于单元素数组

\n
\n\n

第一个案例看起来几乎匹配,但又不完全匹配。两者都是指向不同完整对象的最后一个元素的指针 - 一个完整对象是数组arr,另一个是假设的单元素数组,其元素是arr。并且也不是指向可能存在于各自数组之后的不相关完整对象的指针,如以下 [basic.compound] 引用中的注释所澄清。

\n\n

因此,假设它们代表相同的地址,则应适用另一种情况。

\n\n
\n\n
\n

[碱性化合物]

\n\n

指向或超过对象末尾的指针类型的值表示对象43占用的内存中第一个字节([intro.memory])的地址或对象末尾之后的内存中第一个字节的地址。分别由对象占用的存储空间。\n [ 注意:超过对象末尾的指针 ([expr.add]) 不被视为指向可能位于该地址的对象类型的不相关对象。\ n 当指针值所表示的存储达到其存储持续时间的末尾时,该指针值将变为无效;请参阅 [basic.stc]。\n \xe2\x80\x94 尾注\n ]

\n\n

出于...比较([expr.rel], [expr.eq])的目的,超过 n 个元素的数组 x 的最后一个元素末尾的指针被认为等效于指向假设数组元素的指针x 的 n不是数组元素的 T 类型对象被视为属于具有一个 T 类型元素的数组。...

\n\n

43)对于不在其生命周期内的对象,这是它将占用或用于占用的内存中的第一个字节。

\n
\n\n

arr包含的假设数组都arr具有相同的地址和相同的大小。因此,超过两个数组的一个元素被认为相当于数组边界之外同一地址的指针。

\n\n
\n\n

需要明确的是,数组不能包含填充:

\n\n
\n

[表达式.sizeof]

\n\n

...当应用于类时,结果是该类的对象中的字节数,包括将该类型的对象放入数组中所需的任何填充。\n 将 sizeof 应用于可能重叠的子对象的结果是类型的大小,而不是子对象的大小。\n 当应用于数组时,结果是数组中的总字节数。\n 这意味着包含 n 个元素的数组的大小是 n 倍元素的大小。

\n
\n\n
\n\n

让我们澄清一下,转换为void*不应改变指针值,从而改变相等性。

\n\n
\n

[转化指针]

\n\n

类型为 \xe2\x80\x9c 的纯右值,指向 cv T\xe2\x80\x9d,其中 T 是对象类型,可以转换为类型为 \xe2\x80\x9c 的纯右值,指向 cv void\xe2\x80\x9d .\n 此转换不会改变指针值 ([basic.compound])。

\n
\n