你会看到,当你像这样分配一个变量时,它会着陆在堆栈上。 Stack在你调用的每个函数中保存有关局部变量的小包信息,用简单的话来说。运行时可以检查是否超出了分配堆栈的界限,但是如果在堆栈上的无效位置写入一些数据,则无法检查。堆可以如下所示:
[4个字节 - 一些PTR] [4个字节 - A的第一个元素] [4个字节 - A的第二个元素] ...
当你试图给-1th数组的元素,你实际上试图读取数组之前的四个字节(四个字节,因为它是一个int数组)。您可以覆盖堆栈中保存的一些数据 - 但仍然存在于有效进程的内存中,因此系统没有任何投诉。
尝试在Visual Studio中运行在释放模式验证码:
#include <stdio.h>
int main(int argc, char * argv[])
{
// NEVER DO IT ON PURPOSE!
int i = 0;
int A[5];
A[-1] = 42;
printf("%d\n", i);
getchar();
return 0;
}
编辑:响应意见。
我错过了这个事实,即A是全球性的。它不会被保存在堆栈中,而是在二进制模块的.data段中(大多数情况下),但其余的解释如下:A [-1]仍在进程内存中,因此赋值不会引发AV。但是,这样的赋值会覆盖某些东西,即A之前(可能是指针或二进制模块的其他部分),导致未定义的行为。
请注意,根据编译器(或编译器模式),我的示例可能工作,也可能不工作。例如,在调试模式下,程序返回0--我想,内存管理器会在堆栈帧之间插入一些哨兵数据来捕获缓冲区溢出/溢出等错误。
未定义的行为。如果'A'是一个指向第一个元素的指针,那么你可以这样做,但这更多的是一个负面的索引,而不是一个超出界限的东西。 – chris
我也尝试访问A [11],并且它成功运行。 – 2147483647
@ A.06:是的。 “未定义的行为”包括“它已成功运行”。未定义的行为还包括“可怕的崩溃”和“独角兽从您的电脑屏幕飞出”。它*字面意思*表示你不能以任何形式的可靠性来推理它的行为,这就是为什么UB正是你不想做的。 –