问题 声明为int v [100]的数组但是&(v [100])没有给出警告


我有以下程序:

#include <stdio.h>

int main() {

    int v[100];
    int *p;

    for (p = &(v[0]); p != &(v[100]); ++p)
        if ((*p = getchar()) == EOF) {
            --p;
            break;
        }

    while (p != v)
        putchar(*--p);

    return 0;
}

这是输出 gcc --version 在终端上:

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix

为什么在数组的最后一个之后获取元素的地址不会给我任何警告,而是获取例如地址 v[101] 给了我以下警告

test.c:8:29: warning: array index 101 is past the end of the array (which
      contains 100 elements) [-Warray-bounds]
    for(p = &(v[0]); p != &(v[101]); ++p)
                            ^ ~~~
test.c:5:5: note: array 'v' declared here
    int v[100];
    ^
1 warning generated.

我知道缓冲区范围之外的索引元素是 未定义的行为那么为什么编译器不抱怨第一种情况呢?


7991
2018-03-12 14:22


起源

你的程序将调用 未定义的行为 如果在EOF之前只读取零个字符。 - MikeCAT
你不应该减少 p 上 EOF。 - chqrlie
@chqrlie你说这是因为在下面的while循环中我首先减少了 p 然后取消引用它? - nbro
@nbro:完全正确。 p 将第一个循环读取的最后一个有效字符指向过去,或者因为您读取了100个字符,或者因为您点击了文件末尾。因此不要增加 p 当你从第一个循环中断时。顺便说一句,这将解决MikeCAT在评论中暗示的问题。 - chqrlie


答案:


除非您取消引用指针,否则允许将指针移动到数组的最后一个元素之外,因此如果在按下EOF之前读取了一个或多个字符,则程序有效。

N1256 6.5.2.1数组下标

下标运算符[]的定义   是E1 [E2]与(*((E1)+(E2)))相同。

N1256 6.5.3.2地址和间接运营商

如果操作数是一元*运算符的结果,   既没有运算符也没有&运算符被评估,结果就好像两者都是   省略,除了对运算符的约束仍然适用且结果不是a   左值。同样,如果操作数是[]运算符的结果,则&运算符也不是   评估[]隐含的一元*,结果就像&运算符一样   被删除,[]运算符被更改为+运算符。

N1256 6.5.6加法运算符

而且,如果表达式P指向最后一个   数组对象的元素,表达式(P)+1指向一个过去的最后一个元素   数组对象,如果表达式Q指向一个数组对象的最后一个元素,   表达式(Q)-1指向数组对象的最后一个元素


14
2018-03-12 14:26



老实说,引用不回答我的问题。您还说允许将指针移动到数组的最后一个元素之后(并且引用实际上与您所说的内容无关),但我是 也 实际上获取元素的地址超过最后一个元素(首先访问它)... - nbro
@nbro还有一个引用 &*X 被定义为与...相同 X  ,所以当你将这个与这个答案中的第一个引用结合起来时,你会看到这一点 &v[100] 定义为 (v + 100) 根据最后的报价是合法的 - M.M
换句话说,谈论 *(v+100) 如果您尝试访问指定的内存位置(您可以通过除推送之外的任何上下文执行),则仅定义未定义 & 要么 sizeof 在它之前) - M.M
@ M.M“还有一个引用,&* X被定义为与X相同”,在哪里? - nbro
@nbro C11 6.5.3.2/3,“[...]如果操作数是[]运算符的结果,则[]运算符和[]所暗示的一元*都不会被评估,结果就好像&运算符被删除,[]运算符被更改为+运算符“ - M.M


这是关于与粗略编写的代码的兼容性。

正如MikeCAT引用的那样,对于一个阵列 int ar[N], 表达方式 ar+N 是有效的,并产生一个指向过去位置的指针。虽然这个指针不能被解除引用,但它可以与任何其他指针进行比较,这可以让你写出不错的 for (p = ar; p != ar+N; ++p) 循环。

此外,程序员喜欢编写可读代码,并且可以说,如果你想要指向 i数组的元素,写作 &ar[i] 比写作更清楚地传达你的意图 ar + i

结合这两个,你会得到编写程序员 &ar[N] 获取过去的结束指针,虽然这在技术上是访问无效的数组索引,但是没有任何编译器可以将其实现为除了 ar + N  - 事实上,编译器必须以不同的方式做到这一点。实际上相当远。

因此,由于任何编译器都没有非常严格地对未定义的行为进行推理,它将完成程序员对表达式的期望,因此没有理由不编写它,所以很多人都写了它。现在我们有大量的代码库使用这个习惯用法,这意味着即使是现代编译器的价值跟踪和关于未定义行为的推理也必须支持这个习惯用法来兼容。而且由于Clang的警告本来是有用的,所以这个特别警告的编写是为了不警告一个案件无论如何都会起作用,出于某种错位的迂腐感。


0
2018-03-12 21:06



这是关于与粗略编写的代码的兼容性。 我不同意。在数组末尾构造一个指针没什么大不了的。行为定义明确,这是一种有用的技术。 - Keith Thompson