承接上一篇,我們在 delete p; 前設下中斷點,啟動偵錯並輸入 abcd,觀察記憶體位址 0x00342c40 的內容。字串 abcd 後緊跟著 0x00 結束符,一切看似正常。但錯誤依然發生,甚至改用 delete[] p 也無濟於事。問題的關鍵不在 delete 當下,而在更早的 new char 執行後,記憶體已被佈下「天羅地網」。
重新啟動程式,從一開始就監視記憶體:
// new char 執行前
00342C40 EE FE EE FE EE FE EE (未初始化堆積填充模式)
// new char 執行後
00342C40 CD FD FD FD FD F0 AD (CD = 用戶資料區, FD FD FD FD = 4 個哨兵位元組)
00342C47 BA 0D F0 AD BA 0D F0
輸入 ab 後:
00342C40 61 62 00 FD FD F0 AD (ab\0 覆蓋了第一個 FD)
偵錯執行期(Debug CRT)在使用者分配區後方放置 nNoMansLandSize = 4 個 _bNoMansLandFill = 0xFD 作為邊界哨兵。CheckBytes 函數會在 delete 時檢查這四個位元組是否全為 0xFD;若被覆寫(如 cin 寫入的 \0),即判定為緩衝區溢位,觸發 _RPT3 報錯 DAMAGE: after Normal block。
char *p = new char; 僅分配 1 位元組使用者空間,緊接著 4 個 0xFD。cin >> p 讀入 a 後會自動附加 \0,佔用 2 位元組,導致第一個哨兵 0xFD 被覆寫為 0x00,delete 時檢測失敗。若改為 new char[2],使用者空間變 2 位元組,ab\0 剛好填滿,哨兵倖存,程式不再報錯。
四個 0xFD 就是堆積記憶體的「生死疆界」。Debug 版 CRT 透過在分配區尾部植入已知模式,並在釋放時驗證,以此偵測緩衝區溢位。Release 版移除此機制,因此不會報錯,但隱患依然存在。正確做法是:配合 new[] 使用 delete[],並確保寫入資料不超出分配大小。