你看,当你像这样分配一个变量时,它会落在堆栈上。堆栈包含有关您调用的每个函数中的局部变量的小信息包,用简单的话来说。运行时能够检查您是否超出了分配的堆栈的范围,但不能检查您是否在堆栈的无效位置写入了一些数据。堆栈可能如下所示:
[4 字节 - 一些 ptr][4 字节 - A 的第一个元素][4 字节 - A 的第二个元素] ...
当您尝试分配给数组的第 -1 个元素时,实际上是在尝试读取数组之前的四个字节(四个字节,因为它是一个 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 - 我猜,内存管理器在堆栈帧之间插入一些哨兵数据以捕获缓冲区溢出/欠载等错误。