我有以下计划:
#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.
我知道缓冲区范围之外的索引元素是未定义的行为,那么为什么编译器不抱怨第一种情况呢?
答案 0 :(得分:14)
除非您取消引用指针,否则允许将指针移动到数组的最后一个元素之后,因此如果在按下EOF之前读取了一个或多个字符,则程序有效。
N1256 6.5.2.1数组下标
下标运算符[]的定义 是E1 [E2]与(*((E1)+(E2)))相同。
N1256 6.5.3.2地址和间接运算符
如果操作数是一元*运算符的结果, 无论是运营商还是&amp;运算符被评估,结果就像两者都是 省略,除了对运算符的约束仍然适用且结果不是a 左值。类似地,如果操作数是[]运算符的结果,则&amp;操作员也不 评估[]所暗示的一元*,结果就好像&amp;操作者 被删除,[]运算符被更改为+运算符。
N1256 6.5.6加法运算符
此外,如果表达式P指向最后一个 数组对象的元素,表达式(P)+1指向一个过去的最后一个元素 数组对象,如果表达式Q指向一个数组对象的最后一个元素, 表达式(Q)-1指向数组对象的最后一个元素
答案 1 :(得分:0)
关于与粗俗编写代码的兼容性。
正如MikeCAT所引用的,对于数组int ar[N]
,表达式ar+N
是有效的,并且会产生一个指向过去位置的指针。虽然这个指针不能被解除引用,但它可以与任何其他指向数组的指针进行比较,这样就可以编写好的for (p = ar; p != ar+N; ++p)
循环。
此外,程序员喜欢编写可读代码,并且可以说,如果你想要一个指向数组的i
元素的指针,写&ar[i]
比写ar + i
更清楚地传达你的意图}。
结合这两个,你将得到编写&ar[N]
的程序员来获取过去的结束指针,虽然这在技术上是访问一个无效的数组索引,但是没有任何编译器会将其实现为除了ar + N
- 事实上,编译器必须以不同方式做到这一点。事实上相当远。
因此,由于任何编译器都没有非常严格地对未定义的行为进行推理会使程序员对表达式有所期望,因此没有理由不编写它,所以很多人都写了它。现在我们有大量使用这个习惯用法的代码库,这意味着即使是现代编译器的价值跟踪和关于未定义行为的推理也必须支持这个习惯用法来兼容。而且由于Clang的警告本来是有用的,所以这个特别的警告是为了不警告一个案件,无论如何都会因为某种错位的迂腐而起作用。