C 或 C++ 是否保证 array < array + SIZE?

use*_*445 74 c c++ language-lawyer

假设你有一个数组:

int array[SIZE];
Run Code Online (Sandbox Code Playgroud)

或者

int *array = new(int[SIZE]);
Run Code Online (Sandbox Code Playgroud)

C 或 C++ 是否保证array < array + SIZE,如果是,在哪里?

我知道无论语言规范如何,许多操作系统通过为内核保留虚拟地址空间的顶部来保证此属性。我的问题是语言是否也保证这一点,而不仅仅是绝大多数实现。

举个例子,假设一个操作系统内核处于低内存中,有时会向用户进程提供虚拟内存的最高页面,以响应mmap对匿名内存的请求。如果malloc::operator new[]直接要求mmap分配一个巨大的数组,并且数组的末尾紧靠虚拟地址空间的顶部,从而array + SIZE环绕为零,这是否等于该语言的不合规实现?

澄清

请注意,问题不是问 about array+(SIZE-1),它是数组最后一个元素的地址。那一个保证大于array。问题是关于超过数组末尾的指针,或者p+1何时p是指向非数组对象的指针(所选答案指向的标准部分明确表示以相同方式处理)。

计算器要我解释,为什么这个问题是不一样的这一个。另一个问题询问如何实现指针的总排序。另一个问题本质上归结为一个库如何实现std::less它甚至可以用于指向不同分配对象的指针,标准说只能比较相等,而不是大于和小于。

相比之下,我的问题是关于数组末尾的一个是否总是保证大于数组。我的问题的答案是或否实际上并没有改变你将如何实施std::less,所以另一个问题似乎并不相关。如果与数组末尾的比较是非法的,那么std::less在这种情况下可能只是表现出未定义的行为。(此外,通常标准库是由与编译器相同的人实现的,因此可以自由利用特定编译器的属性。)

tst*_*isl 80

是的。来自第6.5.8第 5 段

如果表达式 P 指向数组对象的一个​​元素,而表达式 Q 指向同一个数组对象的最后一个元素,则指针表达式 Q+1 比较大于 P。

表达式array是 P。表达式array + SIZE - 1指向 的最后一个元素array,也就是 Q。因此:

array + SIZE = array + SIZE - 1 + 1 = Q + 1 > P = array

  • @Wyck - 它并不禁止这样的实现,_只要它确保 `&lt;` 与其一致_。 (10认同)
  • @Wyck,如果我正确阅读 cppreference.com,您可能无法将任何内容放在地址空间的顶部位置:[“指向不是数组元素的对象的指针被视为好像它指向具有一个元素的数组的元素”](https://en.cppreference.com/w/c/language/operator_comparison) (7认同)
  • @ilkkachu:地址未被占用的对象可以放置在地址空间的顶部,或者放置在与空指针的表示相匹配的任何物理地址处。由于大多数重要程序将至少有两个地址未被占用的对象,因此任何地址被占用的对象都必须转移到其他地方的要求不会减少实际有用的存储量。 (7认同)
  • 这是否意味着您无法创建将数组置于地址空间顶部的实现?因为 (array= ((int*)0xFFFFFFFC))+ 1 可能是 0x00000000?(32位地址空间,4字节int示例) (4认同)
  • @Wyck:您似乎将变量“array”、“SIZE”、“P”和“Q”的运行时值与实际虚拟内存地址混为一谈。当然,让指针包含与虚拟内存地址相同的位模式是一个简单的实现,但这不是强制性的。作为一个具体示例,无法直接取消引用 MC68000 上奇数地址处的(16 位)字的指针,因为[对该架构上的奇数地址进行非字节取消引用会引发异常](http://mrjester.hapisan。 com/04_MC68/Sect01Part06/Index.html)。 (4认同)
  • @EricTowers 这是一个反例吗?符合标准的 C 实现不允许在奇数地址上创建对象。我们称之为*对齐*。 (3认同)
  • @EricTowers:需要一个一致的实现来确保它分配的对象的对齐方式与它用来访问它们的任何方式兼容。例如,如果硬件平台具有与任意对齐地址一起工作的 32 位加载指令,以及仅与 32 位对齐地址一起工作的加载多指令,则实现可以在闲暇时确保 32 位对象对齐并使用两种指令来访问它们,或者仅使用前一种指令,然后以任意对齐方式放置对象。 (2认同)

Bar*_*mar 22

C 需要这个。第6.5.8第 5 段说:

指向具有较大下标值的数组元素的指针比较大于指向具有较小下标值的同一数组元素的指针

我确信 C++ 规范中有类似的东西。

这一要求有效地防止了在公共硬件上分配环绕地址空间的对象,因为实现有效实现关系运算符所需的所有簿记是不切实际的。

  • @Neil您可以在末尾处形成一个指针,但不允许取消引用它。 (19认同)
  • 请注意,“array + SIZE”并不指向“array”的任何元素。它指向最后一个元素之后的元素。 (5认同)
  • “有效实现关系运算符所需的簿记”很简单:p &lt; q、p = q 和 p &gt; q 等价于 p−q &lt; 0、p−q = 0 和 p−q &gt; 0,其中 p -q 以地址空间位的宽度计算。只要每个支持的对象小于地址空间大小的一半,p−q 就必须落在正确的区域中。 (4认同)

M.M*_*M.M 13

int *array = new(int[SIZE]);SIZE为零时,保证不成立。

的结果new int[0]必须是一个可以0添加到它的有效指针,但array == array + SIZE在这种情况下,严格小于测试将产生false

  • 你明白了,我应该指出我假设“SIZE &gt; 0”... (10认同)

thr*_*rox 8

这是在 C++ 中定义的,来自 7.6.6.4(当前C++23 草案的p139 ):

当一个整数类型的表达式 J 与指针类型的表达式 P 相加或减去时,结果的类型为 P。

(4.1) — 如果 P 的计算结果为空指针值而 J 的计算结果为 0,则结​​果为空指针值。

(4.2) — 否则,如果 P 指向具有 n 个元素的数组对象 x 的数组元素 i (9.3.4.5),则表达式 P + J 和 J + P(其中 J 的值为 j)指向(可能-假设)数组元素 i + j of x 如果 0 <= i + j <= n 并且表达式 P - J 指向(可能是假设的)数组元素 i ?如果 0 <= i ? j <= n。

(4.3) — 否则,行为未定义。

请注意,4.2 明确具有“<= n”,而不是“< n”。任何大于 size() 的值都未定义,但为 size() 定义。

数组元素的排序在 7.6.9 (p141) 中定义:

(4.1) 如果两个指针指向同一个数组的不同元素,或者指向其子对象,则要求指向下标较高的元素的指针比较大。

这意味着对于 n > 0 的所有明确定义的情况,假设元素 n 将比数组本身(元素 0)更大。


Ric*_*ith 5

C++ 中的相关规则是[expr.rel]/4.1

如果两个指针指向同一个数组的不同元素,或者指向其子对象,则需要指向下标较高的元素的指针比较大。

上面的规则似乎只涵盖了指向数组元素的指针,并array + SIZE没有指向数组元素。然而,正如脚注中提到,一个最后一个指针在这里被视为一个数组元素。相关的语言规则在[basic.compound]/3 中

为指针运算的目的([expr.add])和比较([expr.rel],[expr.eq]),过去的阵列的最后一个元件的端部的指针xÑ元件被认为是等同于指向假设数组元素n的指针x和类型T不是数组元素的对象被认为属于具有一个类型元素的数组T

所以 C++ 保证array + SIZE > array(至少当SIZE > 0),以及&x + 1 > &x任何对象x