指针超出数组类型的末尾进行解引用

6

在c++中,对于数组类型,解引用一个指向超出末尾的指针是否有定义?

考虑以下代码:

#include <cassert>
#include <iterator>

int main()
{
    // An array of ints
    int my_array[] = { 1, 2, 3 };

    // Pointer to the array
    using array_ptr_t = int(*)[3];
    array_ptr_t my_array_ptr = &my_array;

    // Pointer one-past-the-end of the array
    array_ptr_t my_past_end = my_array_ptr + 1;

    // Is this valid?
    auto is_this_valid = *my_past_end;

    // Seems to yield one-past-the-end of my_array
    assert(is_this_valid == std::end(my_array));
}

普遍认为,对一个指向末尾后的位置的指针进行解引用是未定义的行为。但是,对于数组类型的指针是否也适用这一点呢?
从纯粹的指针算术角度来看,似乎这应该是有效的,因为*my_past_end会产生一个指向数组中 将会 存在的第一个元素的指针,这也恰好是原始数组my_array的一个有效的末尾后位置的int*
然而,另一种看待这个问题的方式是,*my_past_end正在生成一个引用到一个不存在的数组,这个引用隐式转换为一个int*。这个引用似乎令人不安。
提供上下文,我的问题是由这个问题引发的,具体来说是这个答案的评论。
编辑:这个问题并非Take the address of a one-past-the-end array element via subscript: legal by the C++ Standard or not?的重复。我问的是这个规则是否也适用于指向数组类型的指针。
编辑2:删除了auto来明确说明my_array_ptr不是一个int*

3
不可以,这将导致未定义行为。 - πάντα ῥεῖ
@πάνταῥεῖ 我同意,但是如果你看一下链接问题中的答案,你会发现并不是每个人都这么认为。 - François Andrieux
@Richard 这不是重复的问题。这个问题特别关注指针是否为数组类型指针时是否有区别。 - François Andrieux
4
不,引用结果指针值用于计算是有效的,但从中解除引用则从未被认为是有效的。 - πάντα ῥεῖ
2
my_array_ptr 是指向整个数组的指针,而不是第一个元素。它是 int(*)[3] 而不是 int * - François Andrieux
显示剩余18条评论
2个回答

8
这是CWG 232。这个问题似乎主要涉及取消引用空指针,但基本上是关于简单取消引用不指向对象的内容。在这种情况下,没有明确的语言规则。
其中一个问题示例为:

Similarly, dereferencing a pointer to the end of an array should be allowed as long as the value is not used:

char a[10];
char *b = &a[10];   // equivalent to "char *b = &*(a+10);"

Both cases come up often enough in real code that they should be allowed.

这基本上与上面的表达式中的 a[10] 部分相同,只是使用了 char 而不是数组类型。
引用:
“常识认为,解引用一个指向末尾后一个位置的指针是未定义的行为。然而,对于指向数组类型的指针是否也适用这一点呢?”
根据规则,不管指针是什么类型,规则都没有区别。 my_past_end 是一个指向末尾后一个位置的指针,因此,无论它是否指向数组类型,解引用它是否会导致未定义的行为并不取决于它所指向的类型。
虽然is_this_valid的类型是int*,它从int(&)[3](数组指针衰减)初始化,因此这里实际上没有从内存中读取任何内容 - 这与语言规则的工作方式无关。 my_past_end是一个指针,其值为超出对象的末尾,这是唯一重要的事情。

1
好的,我们可以假设这是未定义行为,直到问题得到解决。 - Slava
1
@Slava 我不知道你为什么这样说。鉴于存在大量类似的代码,如果措辞发生变化,我预期它将改变以允许这样做。 - Barry
2
那么这是未决行为吗? - 463035818_is_not_a_number
1
@Barry,因为代码是否具有未定义行为并不取决于使用该代码的量,而是取决于该行为是否由标准定义,并且更安全的做法是假定该行为未定义,除非有证据表明它是定义的。 - Slava
@Barry 你知道为什么在过去的十多年里似乎没有关于这个问题的任何活动吗?委员会还在处理这个问题吗? - Brian Bi
显示剩余3条评论

0

我相信它已经很好地定义了,因为它没有解引用超出末尾的指针。

auto is_this_valid = *my_past_end;

my_past_end 的类型是 int(*)[3](指向包含 3 个 int 元素的数组的指针)。因此,表达式 *my_past_end 的类型是 int[3],就像在这种情况下的任何数组表达式一样,它会“衰减”为指向数组对象的初始(零)元素的 int* 类型指针。这个“衰减”是一个编译时操作。因此,初始化只是将类型为 int* 的指针 is_this_valid 初始化为指向 my_array 结尾的位置。没有访问数组对象末尾之外的内存。


下投票者:我可能是错的。如果是这样,你能解释一下为什么吗? - Keith Thompson
1
这只是你的看法,如果你想证明它被充分定义,你应该引用标准。 - melpomene
1
@melpomene:我说它是明确定义的,因为它不会取消引用一个超出末尾指针。标准在哪里描述了不会发生的事情?数组到指针的转换(隐式的)在[C++11]标准的[conf.array]、4.2中有定义,但我认为这是常识。 - Keith Thompson
2
默认情况下,事物是未定义的。对于每一段具有定义行为的代码,都有一部分标准来定义它的功能。*my_past_end 是对 * 运算符的使用,其描述说明了 "结果是一个引用到表达式所指向的对象或函数的左值”。my_past_end 没有指向任何对象,因此行为是未定义的。 - melpomene
2
*x 解引用 x(如果表达式被评估,它就会被解引用)。您似乎错误地将“解引用 x”与“访问 x 指向的内存”混淆了。 - M.M

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