C 或 C++ 是否保证数组<数组 + SIZE?

Does C or C++ guarantee array < array + SIZE?

提问人:user3188445 提问时间:3/2/2021 最后编辑:John Kugelmanuser3188445 更新时间:3/6/2021 访问量:6797

问:

假设你有一个数组:

int array[SIZE];

int *array = new(int[SIZE]);

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

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

例如,假设操作系统内核内存不足,有时会向用户进程提供最高的虚拟内存页,以响应匿名内存请求。如果 或直接调用一个巨大的数组的分配,并且数组的末尾紧邻虚拟地址空间的顶部,从而换行为零,这是否相当于语言的不合规实现?mmapmalloc::operator new[]mmaparray + SIZE

澄清

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

Stackoverflow 要求我澄清为什么这个问题与这个问题不同。另一个问题是如何实现指针的总排序。另一个问题基本上可以归结为库如何实现,使其甚至适用于指向不同分配对象的指针,标准说这些对象只能进行比较相等,而不是大于和小于。std::less

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

C++ C 语言律师

评论

15赞 Mad Physicist 3/2/2021
谁说指针必须是实际的内存地址?
4赞 Barmar 3/2/2021
@S.M.这是关于对指向不同对象的指针进行排序。这个问题只是关于同一数组中的指针。
10赞 dxiv 3/2/2021
@user3188445 从非权威但通常可靠的 cpppreference 中,C++“如果一个指针指向数组的元素或数组元素的子对象,而另一个指针指向数组的最后一个元素,则后一个指针比较更大”。
20赞 Nate Eldredge 3/2/2021
幸运的是,这是有保证的,否则会有很多代码被破坏。这是很常见的for (int *p = array; p < array + SIZE; p++) do_stuff(*p);
7赞 HolyBlackCat 3/3/2021
@АлексейНеудачин 该标准没有提到虚拟地址、页面或描述符表。如果它说这必须是真的,那么任何不这样做的编译器(无论出于任何原因)都会被窃听。而且,在实践中,比较不应该从被比较的地址中读取,因此指针无效并不重要。&obj < &obj + 1<

答:

22赞 Barmar 3/2/2021 #1

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

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

我敢肯定C++规范中有类似的东西。

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

评论

5赞 tstanisl 3/2/2021
请注意,这并不指向 的任何元素。它指向最后一个元素之后的元素。array + SIZEarray
0赞 Neil 3/2/2021
我认为数组被定义为它们的大小 + 1,但我不确定如何查找它。
19赞 Barmar 3/2/2021
@Neil 允许您在末尾形成指针,但不允许取消引用它。
4赞 Eric Postpischil 3/2/2021
“有效实现关系运算符所需的簿记”是微不足道的:p < q、p = q 和 p > q 等价于 p−q < 0、p−q = 0 和 p−q > 0,其中 p−q 以地址空间位的宽度计算。只要每个受支持的对象都小于地址空间大小的一半,p−q 就必须位于正确的区域中。
1赞 Barmar 3/4/2021
@jwdonahue我非常熟悉非传统的实现,但我在 Lisp 机器上使用了 C。
80赞 tstanisl 3/2/2021 #2

是的。摘自第 6.5.8 节第 5 段

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

表达式为 P。该表达式指向 的最后一个元素,即 Q。 因此:arrayarray + SIZE - 1array

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

评论

4赞 Wyck 3/2/2021
这是否意味着您不能创建将数组放在地址空间顶部的实现?因为 (array= ((int*)0xFFFFFFFC))+ 1 可能0x00000000?(32 位地址空间,4 字节 int 示例)
7赞 ilkkachu 3/3/2021
@Wyck,如果我 cppreference.com 没看错的话,您可能无法将任何内容放在地址空间的顶部位置:“指向不是数组元素的对象的指针被视为指向具有一个元素的数组的元素”
7赞 supercat 3/3/2021
@ilkkachu:地址未被采用的对象可以放置在地址空间的顶部,也可以放置在与空指针表示形式匹配的任何物理地址。由于大多数非平凡程序至少有两个地址未被占用的对象,因此要求其地址被占用的任何对象都必须转到其他地方并不会减少实际有用的存储量。
11赞 Toby Speight 3/3/2021
@Wyck - 它不禁止这样的实现,只要它确保<与它一致
4赞 Eric Towers 3/3/2021
@Wyck :您似乎将变量 、 、 和实际虚拟内存地址的运行时值混为一谈。当然,让指针包含与虚拟内存地址相同的位模式是一个简单的实现,但这不是强制性的。举个具体的例子,指向MC68000奇数地址处的(16 位)字的指针不能直接取消引用,因为非字节取消引用该体系结构上的奇数地址会引发异常arraySIZEPQ
-8赞 Алексей Неудачин 3/2/2021 #3

数组保证内部有连续的内存空间。在 C++03 左右之后,向量保证也有一个 .这自动意味着您询问的是真的
,它被称为 连续存储 .可以在此处找到 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0944r0.html
的矢量
&vec[0] ... &vec[vec.size() - 1]

向量的元素是连续存储的,这意味着如果 v 是向量<T, Allocator>其中 T 是布尔以外的某种类型,那么它服从所有 0 <= n < v.size() 的恒等式 &v[n] == &v[0] + n。据推测,经过五年对连续性与缓存交互作用的研究,WG21 清楚地认识到,需要强制要求连续性,并且应该明确禁止非连续性向量实现。

后者来自标准文档。C++03 我猜对了。

评论

0赞 MSalters 3/2/2021
我想你很困惑.*&
2赞 Daniel Wagner 3/2/2021
规范在哪里对阵列做出这种保证?“连续内存空间”究竟是什么意思?(这似乎很难定义,因为规范甚至没有说指针是任何有意义的“数字”。为什么“连续内存空间”意味着“指针不会溢出”?
4赞 user3188445 3/3/2021
我的问题是关于不是.vec[size()]vec[size()-1]
1赞 HolyBlackCat 3/3/2021
如果你正在寻找数组是连续的证据,它在 [dcl.array]/6 中。但正如丹尼尔·瓦格纳(Daniel Wagner)所指出的那样,仅凭这一点并不能严格证明这一点。array < array + SIZE == true
1赞 user3188445 3/3/2021
不,这是有保证的。参见所选答案中链接的 C lsnguage 规范的第 6.5.8 节第 5 段。
8赞 throx 3/3/2021 #4

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

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

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

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

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

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

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

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

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

评论

1赞 user9876 3/4/2021
也就是说你可以创建这样的指针,它没有说明比较的行为方式。
0赞 throx 3/4/2021
你是对的。我假设 OP 对数组成员的强排序感到满意。更新了答案以涵盖此内容。
0赞 user9876 5/5/2021
您的添加仍然没有涵盖这一点。P+n 指向“假设数组元素”。假设含义(在这种情况下)“不存在”。P+n 并不真正指向数组元素。因此,4.1 不适用,因为 P+n 不指向“......数组”。
0赞 throx 5/6/2021
正如理查德·史密斯(Richard Smith)在下面的回答中所指出的,这在[basic.compound]中是有效的,并且4.1明确适用。
13赞 M.M 3/3/2021 #5

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

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

评论

10赞 user3188445 3/3/2021
你明白了,我应该指定我假设......SIZE > 0
0赞 Aiken Drum 3/9/2021
@user3188445 @M.M - 真的,这里的结论是,问题应该是它是否能保证.array <= array + SIZE
5赞 Richard Smith 3/4/2021 #6

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

如果两个指针指向同一数组的不同元素或其子对象,则需要指向具有较高下标的元素的指针才能进行比较。

上面的规则似乎只涵盖指向数组元素的指针,而不指向数组元素。但是,如脚注中所述,此处将 one-past-the-end 指针视为数组元素。相关语言规则在 [basic.compound]/3 中:array + SIZE

出于指针算术 ([expr.add]) 和比较 ([expr.rel], [expr.eq]) 的目的,经过 n 个元素数组的最后一个元素末尾的指针被视为等价于指向假设数组元素 n 的指针,并且非数组元素的类型对象被视为属于具有一个 type 元素的数组。xxTT

所以 C++ 保证(至少当 ),并且对于任何对象。array + SIZE > arraySIZE > 0&x + 1 > &xx