經典面試題:兩個函數各自定義同名陣列 int array[20],第一個函數賦值,第二個函數打印,居然輸出正確?加一個 printf 就亂了?本文從棧幀佈局、寄存器約定、編譯器優化三個維度,徹底揭開這個「巧合」背後的記憶體真相。
void input() {
int array[20];
for (int i = 0; i < 20; i++) array[i] = i;
}
void output() {
int array[20];
for (int i = 0; i < 20; i++) printf("%d\n", array[i]); // 輸出 0..19!
}
int main() {
input();
output(); // 正確輸出
// 若在中間加 printf("any string"); 再呼叫 output() → 輸出亂碼
}
EBP(幀指標/基址指針)指向棧底(當前函數棧幀基址)。ESP(棧指針)指向棧頂(下一個可用位置),隨入棧/出棧移動。push ebp(將調用者EBP入棧)。mov ebp, esp(當前ESP成為新棧底)。sub esp, N(N為區域變數所需位元組,含對齊填充)。EBP 負偏移訪問(如 [ebp-80] 為 array[0])。mov esp, ebp(恢復ESP)→ pop ebp(恢復舊EBP)→ ret。input() 返回後,其棧幀空間邏輯上釋放,但物理記憶體內容殘留(未被覆寫)。output() 被調用時,編譯器分配的棧幀大小、佈局與 input() 完全相同(同樣 int array[20],同樣對齊)。output() 的 array 落在同一塊物理記憶體,讀取到殘留值 → 「正確」輸出。printf 就亂了?printf 內部有自己的區域變數、格式化緩衝區、浮點處理等,會大量使用棧空間,覆寫殘留資料。output() 讀取到的就是 printf 留下的「垃圾」。| 環境 | 行為 | 原因 |
|---|---|---|
| Turbo C (舊) | 不初始化區域變數,殘留值可讀 | 無安全檢查,純粹棧幀復用 |
| MSVC Debug | 區域變數自動填充 0xCC(-858993460) | 為偵錯填充未初始化記憶體,output() 讀到填充值 |
| MSVC Release | input() 被優化掉(無副作用) | 編譯器發現 input() 無觀測副作用,直接刪除呼叫 → 棧無賦值殘留 |
| GCC (Linux) | 視優化等級而定,通常類似 Release | -O0 保留殘留,-O2 可能內聯/刪除 |