我可以获取数组最后一个元素之后的地址吗?

19

3
重复传说中的https://dev59.com/RHNA5IYBdhLWcg3wX8rk,是否合法? - Johannes Schaub - litb
@Johannes - 你说得对,投票关闭。 - Omnifarious
5个回答

24

是的,你可以访问数组末尾之外的地址,但是你不能对其进行取值操作。对于一个包含10个元素的数组,array+10 是有效的。委员会和其他人多次争论过 &array[10] 是否真的会导致未定义的行为(如果确实会,是否应该)。结果是至少根据当前的标准(包括 C 和 C++),这将正式导致未定义的行为。但是,如果有一个编译器实际上无法处理它,那么在任何争论中,没有人能够找到或引用这个编译器。

编辑:我的记忆好像半对半错——这是(部分)提交给委员会的一个官方缺陷报告,至少一些委员会成员(例如 Tom Plum)认为已经更改措辞,使得它不会导致未定义的行为。然而,这个 DR 是2000年的,状态仍然是“起草”状态,因此它是否真的被修复,或者是否可能被修复,还有待商榷(我没有查看 N3090/3092 来确定)。

然而,在 C99 中,显然不会导致未定义的行为。


2
好答案。有趣的历史背景是,我曾经看到过一个C编译器未能正确地解引用&array[N]。这是在HP-1000主机上的HP RTE上。它设置了一个内存“栏杆”就在我的数组上方,所以参考地址不在程序的地址空间中。这甚至还是在1989年C标准之前,所以我认为可以肯定这个编译器已经不存在了。 - Lee-Man
那个DR中的语言不在N3092中。虽然我现在找不到它们,但有几个已关闭的DR依赖于“空lvalue”语言在那个缺陷的建议解决方案中... - James McNellis

10
正如其他答案所指出的,表达式array[10]等同于*(array + 10),这似乎会导致对数组末尾刚过边界的元素进行未定义的解引用。然而,表达式&array[10]等同于&*(array + 10),C99标准明确规定(6.5.3.2地址和间接运算符):

  

一元操作符&返回它的操作数的地址。如果操作数的类型是“type”,则结果的类型是“type”的指针。如果操作数是一元*操作符的结果,则既不评估该运算符也不评估&操作符,并且结果就好像两者都省略了一样,但是运算符的约束仍然适用,并且结果不是lvalue。类似地,如果操作数是[]操作符的结果,则既不评估&操作符也不评估由[]隐含的一元*,并且结果就好像删除了&操作符并将[]操作符更改为+操作符。

因此,&array[10]没有任何未定义的行为-没有发生解引用操作。

2
不,它是未定义的。 array[10] 相当于 *(array + 10)。换句话说,你已经解引用了一个无效的指针。

4
但他没有取消引用它... - Jakob Borg
3
关于&*p是否在技术上构成无操作或者是解引用后跟随一个取地址符,存在一定的争议。 - Tyler McHenry
1
"&array[10]" 是合法的,且不会引用元素。 - David R Tribble
1
@Loadmaster 相当正式 - 在 C++ 和 C89 中,它确实会取消引用元素,从而导致 C89 中的未定义行为。在 C++ 中,如果数组后面没有元素,则会导致未定义行为*(要确保,您必须将该子数组放入多维数组中 - 然后保证有一个元素刚好在另一个数组之后,而不需要填充)。在 C++ 中,如果存在这样的元素,同样是可以的,因为这样的指针本身并不无效 - 指向数组末尾之后的指针是有效的。只是像上面所说的那样约束取消引用它。 - Johannes Schaub - litb
2
“解引用”指从指针中获取lvalue。它并不意味着读取一个值。标准在5.3.1/1处举例说明:“[注:可以对不完整类型(cv void除外)的指针进行解引用。因此获得的lvalue只能以有限的方式使用(例如,用于初始化引用);这个lvalue不能转换为rvalue,请参见4.1。]”另请参见3.8/5处的另一个示例:“这样的指针可能会被解引用,但所得到的lvalue只能以有限的方式使用,如下所述。” - Johannes Schaub - litb

1

array[10] 相当于 *(array + 10)(也相当于 10[array]),所以 &array[10] 的作用类似于从 *(array+10) 中删除 *。任何像样的编译器都应该生成相同的代码(并发出相同的警告)。


-3

这实际上是一个相当简单的问题。

你所做的只是指针数学运算,没有任何无效的地方。如果您尝试以某种方式使用生成的指针,则取决于您的进程是否可以访问该指针指向的内存,如果可以,则其权限是什么?

foo.cc:

#include <iostream>

using namespace std;
int main() {
        int array[10];
        int *a = array + 10;
        int *b = &array[10];
        int *c = &array[3000];

        cerr << "Pointers values are: " << a << " " << b << " " << c << endl;
        return 0;
}

编译:

g++ -Werror -Wall foo.cc -o foo

应该不会出现警告,因为 int *b = &array[10] 是有效的

实际上,我添加的后续行也指出了指针运算的一些内容。

不仅 foo 能编译通过,而且还能毫无问题地运行:

 ./foo
  Pointers are: 0x7fff1d356e68 0x7fff1d356e68 0x7fff1d359d20

基本上,数组语法 foo[idx] 是指针数学的一种简化和清晰表现方式。
如果在 c99 方面存在某些困惑,则标准不会以任何方式影响此数学,并且是良好定义的。
C99 中相同的事情:
#include <stdio.h>

int main() {
        int array[10];
        int *a = array + 10;
        int *b = &array[10];
        int *c = &array[3000];

        fprintf(stderr, "Pointers are: %p, %p, %p\n" , a , b , c );
        return 0;
}

然后:

gcc -std=c99 -Wall -Werror -o foo foo.c

./foo

输出:

Pointers are: 0x7fff2c7d22c8, 0x7fff2c7d22c8, 0x7fff2c7d5180

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