C++:在数组末尾2个或更多位置处获取地址是否合法?

6

通过下标获取一个超出数组末尾的元素的地址:在C++标准中是否合法?看来,有一种特定于语言的方法可以获得比数组结尾多一个元素的地址。

如果没有解引用,为什么会超出2或2000000个元素就成为问题呢?

看一下这个简单的循环:

int array[];
...
for (int i = 0: i < array_max; ++i)
{
       int * x = &array[i *2];      // Is this legal
       int y=0;
       if (i * 2 < array_max)       // We check here before dereference
       {
              y = *x;               // Legal dereference
       }
       ...
}

为什么或者在什么情况下会出现未定义的情况,实际上它只是将指针设置为某个值,如果它没有被引用,为什么会变成未定义?

更具体地说,除了预期发生的事情之外,还可能有什么例外呢?


1
指向那里的指针是未定义行为。 - chris
2
因为它在比较或减法方面可能表现不正常。例如,(p + 2000000000) - p 可能不等于 2000000000。 - Raymond Chen
你最多只能希望 &arr[size+1] 等同于 arr + size + 1,但即使如此也是未定义行为。仅仅拥有一个指向数组外部的指针(不包括超出末尾的指针)就是未定义行为。从技术上讲,任何导致这种指针的算术运算都会导致未定义行为,因此即使是 arr - 2 + 2 也是未定义行为,但这只是本评论的第一部分。 - chris
请记住,按照您预期的方式运行属于未定义行为范畴。不幸的是,许多其他事情也属于这个范畴,根据定义,您将得到哪个结果是不可预测的。 - Mark Ransom
@MarkRansom 如果它总是按预期执行,那么它就不能被称为未定义,必须存在一些不可预测的结果,这正是我所寻找的。 - Glenn Teitelbaum
显示剩余4条评论
3个回答

8

将地址扩展到数组末尾的关键问题是分段架构:您可能会溢出指针可表示的范围。现有规则已经造成了一定程度的痛苦,因为这意味着最后一个对象不能正好在段边界上。然而,形成这个地址的能力已经得到了很好的确认。


即使没有解除引用,为什么这仍然是一个问题? - zneak
1
@LightnessRacesinOrbit:这些并没有解释为什么超过段边界的“对象结束”指针是一个问题。 - MSalters
@zneak:使用分段指针时,指针比较是在单个段内完成的。如果一个对象恰好在段的边界上结束,那么它后面的指针就不会指向同一段,因此无法进行比较。 - MSalters

4
由于array[i *2]等同于*((array) + (i*2)),因此我们应该查看指针加法的规则。C++11 §5.7 表示:
如果指针操作数和结果都指向同一数组对象或者超过该数组对象的最后一个元素,则评估不得产生溢出;否则,行为是未定义的。
因此,即使您没有对指针执行间接引用(更不用说您执行了间接引用,因为我在开头给出了表达式等效性),您仍会面临未定义的行为。

@MSalters:为什么? - Lightness Races in Orbit
如果我没记错的话,合法化 &array[last] - MSalters
@MSalters:是的,这就是你正在评论的答案中引用的内容,不是吗?关键在于这个问题是关于&array[last+1]而不是&array[last] - Lightness Races in Orbit
@LightnessRacesinOrbit:完全意识到这一点,但重点是你的最后一句话假定间接引用以两种形式发生。我的陈述是,这两种语法都不执行间接引用。它们只是形成一个地址,只有对指定对象的后续使用可能触发间接引用。 - MSalters
@LightnessRacesinOrbit:再次从记忆中回忆起,这是 C++11 清理整个内存访问描述的一部分。特别是在多线程情况下,需要更清晰地定义何为读取和/或写入。仅仅形成一个指针不应该算作读取或写入,这意味着必须引入 5.7 的新措辞来正确处理 &[last] 这种特殊情况。(对于 i==N,&a[i]==a+i 总是有点不幸。) - MSalters
显示剩余3条评论

3

在实际中,它只是将指针设置为某个值。

理论上讲,只有一个指向无效位置的指针是不允许的。

指针不是整数:它们是指向其他事物或空指针的东西。

你不能随意将它们设置为任何数字。

在这个例子中,除了赋值和限定引用之外没有别的操作-只有在验证该值后才使用,问题是为什么设置该值会成为问题?

是的,你必须非常不幸才会遇到这样做的实际后果。“未定义行为”并不意味着“总是崩溃”。 为什么标准要规定这样的操作的语义呢?你认为这样的语义应该是什么?


1
但是您可以将它们转换为整数(或更确切地说是uintptr_t),并对其执行任何所需操作,然后再将其转换回来(如果它们是有效的)。 - user2345215
1
@user2345215:是的,就像你可以使用reinterpret_cast将它们转换为ChickenSoup*一样。这并不会改变它们的预期用途,而规则也是基于此制定的。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接