你會看到,當你像這樣分配一個變量時,它會着陸在堆棧上。 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正是你不想做的。 –