問題源自一段簡單的代碼:
void main()
{
char *p = new char;
cin>>p;
cout<<p[2];
delete p;
}
在以上代碼中,如果你輸入:abcd,那麼如你所望,你會看到"正確"的輸 出"c"。但是會有錯誤提示出現:
Debug Error!
Program: test.exe
DAMAGE: after Normal block(#64) at 0x003429f8
更離奇的是,如果將代碼改為如下的代碼:
void()
{
char *p = new char;
cin>>p;
cout<<p;
delete p;
}
如果只輸入一個字符a,那麼依然報錯。是不是奇怪,分配了一個字符,輸入了一個 字符,那麼錯在哪裡? 注意,最開始那行Debug Error!說明這是在Debug編譯模式下才有的 提示,如果你換到release頻道,那麼此提示不再出現,你成功得到了"c",仿佛 程序一切正常。
一個奇怪的現象是,如果去掉delete p這條語句,這個運行時錯誤消失了,甚至你在 debug模式下也看不到這個提示。 問題何在?
以前我遇到過這種情況,分析後歸結為一個結論:在debug模式下系統有一定的機制偵測 到內存的非法訪問。然後就放過這個問題。這個結論說了等於沒說,關鍵在於,這種機制的 具體運做過程。這次我下了狠心,不入虎穴,焉得虎子。我決定追進源代碼裡邊去。 把編譯 環境設置成debug模式,很顯然,問題出在delete p上,在這條語句設置斷點,按F5,程序運 行到這條語句前自動暫停,然後按F11。
Welcome to the Source Code World!
首先來到DELOP.CPP文件中,這個文件短小精悍,只有一個函數
void __cdecl operator delete(void *p) _THROW0()
{ // free an allocated object
free(p);
}
沒有任何有用的信息,那就繼續追進free(p)裡。 不一會,我們追到了DBGHEAP.C 中,你從文件名可以看出,這是在debug模式下才能進入的文件。
最後在_CRTIMP void __cdecl _free_dbg(void * pUserData, int nBlockUse )中的這 條語句
if (!CheckBytes(pbData(pHead) + pHead->nDataSize, _bNoMansLandFill, nNoMansLandSize))
_RPT3(_CRT_ERROR, "DAMAGE: after %hs block (#%d) at 0x% 08X.\n",
szBlockUseName[_BLOCK_TYPE(pHead->nBlockUse)],
pHead->lRequest,
(BYTE *) pbData(pHead));
前受阻。 是不是覺得這這模塊巨 可怕,呵呵,靜下心來,很簡單,因為有if存在,那麼CheckBytes()一定是執行某種檢驗 ,如果檢驗失敗,調用_RPT3()函數 在MSDN中,對_RPT函數族有這樣的解釋:
Track an application''s progress by generating a debug report (debug version only).
_RPT3的作用就是產生一個錯誤報告。
好了,知道了這一點就足夠了,它對我們來說沒什麼意義了。那麼只剩下CheckBytes了, 深呼吸幾口,好了,讓我們進去吧。
static int __cdecl CheckBytes(unsigned char * pb, unsigned char bCheck, size_t nSize)
{
int bOkay = TRUE;
while (nSize--)
{
if (*pb++ != bCheck)
{
_RPT3(_CRT_WARN, "memory check error at 0x%08X = 0x%02X, should be 0x%02X.\n",
(BYTE *)(pb-1),*(pb-1), bCheck);
bOkay = FALSE;
}
}
return bOkay;
}
你看到了,這個函數只調用了_RPT3,再也沒有其他的調用,看來,我們到頭了。 下面是微軟的程序員為這個函數寫的注釋的一部分:
*Purpose:
* verify byte range set to proper value
*Return:
* TRUE - if all bytes in range equal bcheck
* FALSE otherwise
再明顯不過了,這個函數檢驗一定范圍的位是否設定為了 正確的值(就是傳進來的那麼bCheck),如果正確,返回bOkay=TRUE,否則,返回 bOkay=FALSE. 都挖完了,再也沒有任何有用的信息,我們仍舊不知道微軟是如何進行校驗的 ,眼前依然一片黑暗。如果還有黎明的曙光,那麼只能從傳入的參數身上發出,呵呵,它們 三肩負著我們的厚望啊。看看第一個參數unsigned char* pb。 if (*pb++ != bCheck)這條 語句告訴我們要將pb所指內存地址的指與bCheck比較,那麼我們還有最後一線希望:直接監 視內存。
欲知後事如何,且聽下回分解 :)