【问题标题】:Shall a pointer past the end of the last array element compare equal to a pointer past the end of the whole array?越过最后一个数组元素末尾的指针是否应该等于越过整个数组末尾的指针?
【发布时间】:2020-04-29 18:54:34
【问题描述】:

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

int main()
{
    constexpr int arr[3] = {};
    static_assert((void*)(arr + 3) == (void*)(&arr + 1));
}

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

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

【问题讨论】:

  • 从过去指针的定义看来(void*)(&arr + 1) == (void*)(&arr[1] + 1)必须持有,但&arr[2]的状态是模糊的
  • @M.M (void*)(&arr + 1) == (void*)(&arr[1] + 1) 必须持有的结束指针的定义似乎很清楚我似乎不清楚。
  • &arr + 1 表示arr 之后的内存中第一个字节的地址,&arr[1] + 1 表示arr[1] 之后的内存中第一个字节的地址,它们必须是同一个字节数组中没有尾随填充
  • @M.M 好的,如果数组的第一个元素与数组is fixed 具有相同的地址的问题,那么我会说对于 2 个元素的数组很清楚。但是,对于具有 3 个元素的数组,为什么超过 arr[2] 末尾的指针不能与指向 arr[1] 的指针进行比较?
  • 哦,您是在试图提出数组元素可能不按其下标顺序的论点吗?如果是这样,那么这个问题是不诚实的。 FWIW 我不相信该标准支持这样的事情,即使它支持,如果有人愿意迎合这种迂腐的水平,这将是一个可以纠正的缺陷。例如。你刚刚链接到的东西。

标签: c++ arrays pointers language-lawyer


【解决方案1】:

最新标准草案中的相关规则:

[intro.object]

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

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


[表达式.eq]

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

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

  • 否则,如果...都表示相同的地址,它们比较相等。

79) [basic.compound] 中规定,非数组元素的对象被认为属于单元素数组

第一种情况似乎几乎匹配,但不完全匹配。 两者都是指向不同完整对象的最后一个元素的指针——一个完整对象是数组arr,另一个是假设的单元素数组,其元素是arr。也不是指向可能存在于它们各自数组之后的不相关完整对象的指针,正如以下 [basic.compound] 引用中的注释所阐明的那样。

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


[basic.compound]

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

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

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

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


请注意,数组不能包含填充:

[expr.sizeof]

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


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

[conv.ptr]

“指向 cv T 的指针”类型的纯右值,其中 T 是对象类型,可以转换为“指向 cv void 的指针”类型的纯右值。 此转换未更改指针值([basic.compound])。

【讨论】:

  • 您似乎假设&arr[2]arr + 2 的意思相同,但标准doesn't seem clear 就在上面
  • @M.M 看来我是,我想这个例子的明确性依赖于此。
  • @LanguageLawyer,更有趣的发现是代码的行为是否随着 Visual Studio 中的更改而有所不同。
  • “两者都是指向不同完整对象的最后一个元素的指针 - 一个完整的对象是数组 arr” 这句话与您在其下方略举的内容不兼容. &arr[2](或arr + 2)不是arr 末尾的指针,而是arr[1] 末尾的指针。这不是一个完整的对象。
  • @M.M 我已经更新了这个问题。另外,我已将数组长度从 2 更改为 3
猜你喜欢
  • 1970-01-01
  • 2018-01-02
  • 2011-06-13
  • 1970-01-01
  • 2019-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-17
相关资源
最近更新 更多