問(wèn)題源自一段簡(jiǎn)單的代碼:
Debug Error!
Program: test.exe
DAMAGE: after Normal block(#64) at 0x003429f8
更離奇的是,如果將代碼改為如下的代碼:
一個(gè)奇怪的現(xiàn)象是,如果去掉delete p這條語(yǔ)句,這個(gè)運(yùn)行時(shí)錯(cuò)誤消失了,甚至你在debug模式下也看不到這個(gè)提示。 問(wèn)題何在?
以前我遇到過(guò)這種情況,分析后歸結(jié)為一個(gè)結(jié)論:在debug模式下系統(tǒng)有一定的機(jī)制偵測(cè)到內(nèi)存的非法訪問(wèn)。然后就放過(guò)這個(gè)問(wèn)題。這個(gè)結(jié)論說(shuō)了等于沒(méi)說(shuō),關(guān)鍵在于,這種機(jī)制的具體運(yùn)做過(guò)程。這次我下了狠心,不入虎穴,焉得虎子。我決定追進(jìn)源代碼里邊去。 把編譯環(huán)境設(shè)置成debug模式,很顯然,問(wèn)題出在delete p上,在這條語(yǔ)句設(shè)置斷點(diǎn),按F5,程序運(yùn)行到這條語(yǔ)句前自動(dòng)暫停,然后按F11。
Welcome to the Source Code World!
首先來(lái)到 DELOP.CPP 文件中,這個(gè)文件短小精悍,只有一個(gè)函數(shù)
最后在_CRTIMP void __cdecl _free_dbg(void * pUserData, int nBlockUse )中的這條語(yǔ)句
Track an application''s progress by generating a debug report (debug version only).
_RPT3的作用就是產(chǎn)生一個(gè)錯(cuò)誤報(bào)告。
好了,知道了這一點(diǎn)就足夠了,它對(duì)我們來(lái)說(shuō)沒(méi)什么意義了。那么只剩下CheckBytes了,深呼吸幾口,好了,讓我們進(jìn)去吧。
還記得我們?cè)赿elete p;前設(shè)的斷點(diǎn)嗎?好,讓我們重新開(kāi)始調(diào)試,按F5,從控制臺(tái)輸入"abcd",然后到這條語(yǔ)句前停止了,查看變量p的值,是0x00342c40,那好,打開(kāi)vc監(jiān)視內(nèi)存的窗口memory,我們查看這個(gè)地址的值:
此后的內(nèi)存情況不再用圖片顯示。只用紅色標(biāo)志的內(nèi)存表示發(fā)生了變化的內(nèi)存
看到了嗎?你的寶貝"abcd"乖乖地躺在內(nèi)存中,其后跟了一個(gè)0x00,那表示''/0'',字符串結(jié)束標(biāo)志。一切都很正常,到底哪里出錯(cuò)了?難道是delete p用錯(cuò)了,而應(yīng)該用delete[] p?try it,你會(huì)發(fā)現(xiàn)依然有相同的錯(cuò)誤。
從這段內(nèi)存中仍然看不出問(wèn)題,仿佛一切風(fēng)平浪靜,其實(shí)是我們來(lái)晚了,在delete p前,內(nèi)存早已經(jīng)發(fā)生了翻天覆地的變化。 再一次重新進(jìn)入程序,這次我們從一開(kāi)始就監(jiān)視內(nèi)存。
00342C40 EE FE EE FE EE FE EE 鉿鉿鉿.
00342C47 FE EE FE EE FE EE FE .
這是char *p = new char,執(zhí)行前的內(nèi)存。下面是執(zhí)行后的:
00342C40 CD FD FD FD FD F0 AD 妄.
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
看不出什么問(wèn)題,再往下執(zhí)行吧:(cin>>p, 這次我們輸入ab)
00342C40 61 62 00 FD FD F0 AD ab. 瓠
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
ab正確地放進(jìn)了內(nèi)存中,而且你可以看到cin還體貼地在ab后為你放了一個(gè)''/0''
還是沒(méi)有什么問(wèn)題?再往下走就是cout<<p了,它不會(huì)改動(dòng)內(nèi)存,再往下就到了delete p,到那時(shí)一切都晚了。 沒(méi)錯(cuò),就是這一步,靜靜的內(nèi)存中早已經(jīng)翻江倒海。
還記得static int __cdecl CheckBytes(unsigned char * pb, unsigned char bCheck, size_t nSize)中的bCheck, nSize嗎? 如果當(dāng)初你也監(jiān)視變量的話,會(huì)發(fā)現(xiàn)bCheck = 253, nSize = 4。這就是這個(gè)內(nèi)存?zhèn)蓽y(cè)機(jī)制的命門(mén)。小時(shí)候喜歡看武打片,有一部叫做〈鷹爪鐵布衫〉的,當(dāng)時(shí)令我如癡如醉啊,看過(guò)的人一定還記得最后殺那老頭的時(shí)候是先在他天靈上一拍,接著再在褲襠上捏一把,呵呵,bCheck就是天靈,nSize就是褲襠。
把253轉(zhuǎn)換為16進(jìn)制,是什么,沒(méi)錯(cuò),是FD。呵呵,別忙往下看,想一想,你找到真相了嗎? 再看一眼char *p = new char執(zhí)行后的內(nèi)存,你發(fā)現(xiàn)了什么?p指向0x00342c40那個(gè)字節(jié)的值為CD,這是屬于你的內(nèi)存,看看后邊跟的是什么,不多不少, 恰恰是4個(gè)FD,恰恰是nSize個(gè)bCheck!
這個(gè)偵測(cè)內(nèi)存非法訪問(wèn)的機(jī)制現(xiàn)在已經(jīng)被我們開(kāi)膛破肚了。微軟在你申請(qǐng)的空間后加上四個(gè)FD,如果你訪問(wèn)了你非法訪問(wèn)內(nèi)存,那么這些內(nèi)存的內(nèi)容將被改變(有一個(gè)問(wèn)題我沒(méi)有解決,我不知道FD代表什么,望知道的兄弟教我),在delete時(shí),將檢查由new產(chǎn)生的''/0''結(jié)束符后是否有連續(xù)四個(gè)字節(jié)都是FD,如果有證明沒(méi)有發(fā)生非法內(nèi)存訪問(wèn),如果沒(méi)有,那就該讓_RPT3老兄出馬了。
對(duì)于
00342C40 61 00 FD FD FD F0 AD a. .
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
雖然你只用了你申請(qǐng)的內(nèi)存,但是cin為了討好你給你加那個(gè)''/0'',覆蓋了一個(gè)FD,這樣,delete時(shí)照樣報(bào)錯(cuò),如果你這樣做
00342C40 61 00 FD FD FD FD AD a. .
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
那么程序?qū)⒉粫?huì)報(bào)錯(cuò)。 四個(gè)FD就是內(nèi)存的生死疆界,超過(guò)這個(gè)疆界,呵呵,聽(tīng)見(jiàn)遠(yuǎn)方傳來(lái)的崩潰的聲音了嗎? 到此,一切真相大白,山高月小,水落石出!
(尾聲:以上所有內(nèi)容皆來(lái)自筆者獨(dú)立分析,其中難免有錯(cuò),更甚者,也許我大錯(cuò)特錯(cuò),壓根就不是這樣的機(jī)制。如果你發(fā)現(xiàn)其中有不正確的地方,請(qǐng)指出,謝謝,在下感激不盡)。
void main() { char *p = new char; cin>>p; cout<<p[2]; delete p; }在以上代碼中,如果你輸入:abcd,那么如你所望,你會(huì)看到"正確"的輸出"c"。但是會(huì)有錯(cuò)誤提示出現(xiàn):
Debug Error!
Program: test.exe
DAMAGE: after Normal block(#64) at 0x003429f8
更離奇的是,如果將代碼改為如下的代碼:
void() { char *p = new char; cin>>p; cout<<p; delete p; }如果只輸入一個(gè)字符a,那么依然報(bào)錯(cuò)。是不是奇怪,分配了一個(gè)字符,輸入了一個(gè)字符,那么錯(cuò)在哪里? 注意,最開(kāi)始那行Debug Error!說(shuō)明這是在Debug編譯模式下才有的提示,如果你換到release頻道,那么此提示不再出現(xiàn),你成功得到了"c",仿佛程序一切正常。
一個(gè)奇怪的現(xiàn)象是,如果去掉delete p這條語(yǔ)句,這個(gè)運(yùn)行時(shí)錯(cuò)誤消失了,甚至你在debug模式下也看不到這個(gè)提示。 問(wèn)題何在?
以前我遇到過(guò)這種情況,分析后歸結(jié)為一個(gè)結(jié)論:在debug模式下系統(tǒng)有一定的機(jī)制偵測(cè)到內(nèi)存的非法訪問(wèn)。然后就放過(guò)這個(gè)問(wèn)題。這個(gè)結(jié)論說(shuō)了等于沒(méi)說(shuō),關(guān)鍵在于,這種機(jī)制的具體運(yùn)做過(guò)程。這次我下了狠心,不入虎穴,焉得虎子。我決定追進(jìn)源代碼里邊去。 把編譯環(huán)境設(shè)置成debug模式,很顯然,問(wèn)題出在delete p上,在這條語(yǔ)句設(shè)置斷點(diǎn),按F5,程序運(yùn)行到這條語(yǔ)句前自動(dòng)暫停,然后按F11。
Welcome to the Source Code World!
首先來(lái)到 DELOP.CPP 文件中,這個(gè)文件短小精悍,只有一個(gè)函數(shù)
void __cdecl operator delete(void *p) _THROW0() { // free an allocated object free(p); }沒(méi)有任何有用的信息,那就繼續(xù)追進(jìn)free(p)里。 不一會(huì),我們追到了 DBGHEAP.C 中,你從文件名可以看出,這是在debug模式下才能進(jìn)入的文件。
最后在_CRTIMP void __cdecl _free_dbg(void * pUserData, int nBlockUse )中的這條語(yǔ)句
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));前受阻。 是不是覺(jué)得這這模塊巨可怕,呵呵,靜下心來(lái),很簡(jiǎn)單,因?yàn)橛衖f存在,那么CheckBytes()一定是執(zhí)行某種檢驗(yàn),如果檢驗(yàn)失敗,調(diào)用_RPT3()函數(shù) 在MSDN中,對(duì)_RPT函數(shù)族有這樣的解釋:
Track an application''s progress by generating a debug report (debug version only).
_RPT3的作用就是產(chǎn)生一個(gè)錯(cuò)誤報(bào)告。
好了,知道了這一點(diǎn)就足夠了,它對(duì)我們來(lái)說(shuō)沒(méi)什么意義了。那么只剩下CheckBytes了,深呼吸幾口,好了,讓我們進(jìn)去吧。
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; }你看到了,這個(gè)函數(shù)只調(diào)用了_RPT3,再也沒(méi)有其他的調(diào)用,看來(lái),我們到頭了。 下面是微軟的程序員為這個(gè)函數(shù)寫(xiě)的注釋的一部分:
*Purpose: * verify byte range set to proper value *Return: * TRUE - if all bytes in range equal bcheck * FALSE otherwise再明顯不過(guò)了,這個(gè)函數(shù)檢驗(yàn)一定范圍的位是否設(shè)定為了正確的值(就是傳進(jìn)來(lái)的那么bCheck),如果正確,返回bOkay=TRUE,否則,返回bOkay=FALSE. 都挖完了,再也沒(méi)有任何有用的信息,我們?nèi)耘f不知道微軟是如何進(jìn)行校驗(yàn)的,眼前依然一片黑暗。如果還有黎明的曙光,那么只能從傳入的參數(shù)身上發(fā)出,呵呵,它們?nèi)缲?fù)著我們的厚望啊。看看第一個(gè)參數(shù)unsigned char* pb。 if (*pb++ != bCheck)這條語(yǔ)句告訴我們要將pb所指內(nèi)存地址的指與bCheck比較,那么我們還有最后一線希望: 直接監(jiān)視內(nèi)存 。
還記得我們?cè)赿elete p;前設(shè)的斷點(diǎn)嗎?好,讓我們重新開(kāi)始調(diào)試,按F5,從控制臺(tái)輸入"abcd",然后到這條語(yǔ)句前停止了,查看變量p的值,是0x00342c40,那好,打開(kāi)vc監(jiān)視內(nèi)存的窗口memory,我們查看這個(gè)地址的值:
庫(kù)精華珍藏版/vckbaseBE30.chm::/images/newdelete.gif)
此后的內(nèi)存情況不再用圖片顯示。只用紅色標(biāo)志的內(nèi)存表示發(fā)生了變化的內(nèi)存
看到了嗎?你的寶貝"abcd"乖乖地躺在內(nèi)存中,其后跟了一個(gè)0x00,那表示''/0'',字符串結(jié)束標(biāo)志。一切都很正常,到底哪里出錯(cuò)了?難道是delete p用錯(cuò)了,而應(yīng)該用delete[] p?try it,你會(huì)發(fā)現(xiàn)依然有相同的錯(cuò)誤。
從這段內(nèi)存中仍然看不出問(wèn)題,仿佛一切風(fēng)平浪靜,其實(shí)是我們來(lái)晚了,在delete p前,內(nèi)存早已經(jīng)發(fā)生了翻天覆地的變化。 再一次重新進(jìn)入程序,這次我們從一開(kāi)始就監(jiān)視內(nèi)存。
00342C40 EE FE EE FE EE FE EE 鉿鉿鉿.
00342C47 FE EE FE EE FE EE FE .
這是char *p = new char,執(zhí)行前的內(nèi)存。下面是執(zhí)行后的:
00342C40 CD FD FD FD FD F0 AD 妄.
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
看不出什么問(wèn)題,再往下執(zhí)行吧:(cin>>p, 這次我們輸入ab)
00342C40 61 62 00 FD FD F0 AD ab. 瓠
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
ab正確地放進(jìn)了內(nèi)存中,而且你可以看到cin還體貼地在ab后為你放了一個(gè)''/0''
還是沒(méi)有什么問(wèn)題?再往下走就是cout<<p了,它不會(huì)改動(dòng)內(nèi)存,再往下就到了delete p,到那時(shí)一切都晚了。 沒(méi)錯(cuò),就是這一步,靜靜的內(nèi)存中早已經(jīng)翻江倒海。
還記得static int __cdecl CheckBytes(unsigned char * pb, unsigned char bCheck, size_t nSize)中的bCheck, nSize嗎? 如果當(dāng)初你也監(jiān)視變量的話,會(huì)發(fā)現(xiàn)bCheck = 253, nSize = 4。這就是這個(gè)內(nèi)存?zhèn)蓽y(cè)機(jī)制的命門(mén)。小時(shí)候喜歡看武打片,有一部叫做〈鷹爪鐵布衫〉的,當(dāng)時(shí)令我如癡如醉啊,看過(guò)的人一定還記得最后殺那老頭的時(shí)候是先在他天靈上一拍,接著再在褲襠上捏一把,呵呵,bCheck就是天靈,nSize就是褲襠。
把253轉(zhuǎn)換為16進(jìn)制,是什么,沒(méi)錯(cuò),是FD。呵呵,別忙往下看,想一想,你找到真相了嗎? 再看一眼char *p = new char執(zhí)行后的內(nèi)存,你發(fā)現(xiàn)了什么?p指向0x00342c40那個(gè)字節(jié)的值為CD,這是屬于你的內(nèi)存,看看后邊跟的是什么,不多不少, 恰恰是4個(gè)FD,恰恰是nSize個(gè)bCheck!
這個(gè)偵測(cè)內(nèi)存非法訪問(wèn)的機(jī)制現(xiàn)在已經(jīng)被我們開(kāi)膛破肚了。微軟在你申請(qǐng)的空間后加上四個(gè)FD,如果你訪問(wèn)了你非法訪問(wèn)內(nèi)存,那么這些內(nèi)存的內(nèi)容將被改變(有一個(gè)問(wèn)題我沒(méi)有解決,我不知道FD代表什么,望知道的兄弟教我),在delete時(shí),將檢查由new產(chǎn)生的''/0''結(jié)束符后是否有連續(xù)四個(gè)字節(jié)都是FD,如果有證明沒(méi)有發(fā)生非法內(nèi)存訪問(wèn),如果沒(méi)有,那就該讓_RPT3老兄出馬了。
對(duì)于
char *p = new char; cin>>p; cout<<p; delete p;這段代碼,如果只輸入一個(gè)字符a,cin>>p執(zhí)行后的內(nèi)存為
00342C40 61 00 FD FD FD F0 AD a. .
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
雖然你只用了你申請(qǐng)的內(nèi)存,但是cin為了討好你給你加那個(gè)''/0'',覆蓋了一個(gè)FD,這樣,delete時(shí)照樣報(bào)錯(cuò),如果你這樣做
char *p = new char[2];那么cin>>p后內(nèi)存為
00342C40 61 00 FD FD FD FD AD a. .
00342C47 BA 0D F0 AD BA 0D F0 ..瓠...
那么程序?qū)⒉粫?huì)報(bào)錯(cuò)。 四個(gè)FD就是內(nèi)存的生死疆界,超過(guò)這個(gè)疆界,呵呵,聽(tīng)見(jiàn)遠(yuǎn)方傳來(lái)的崩潰的聲音了嗎? 到此,一切真相大白,山高月小,水落石出!
(尾聲:以上所有內(nèi)容皆來(lái)自筆者獨(dú)立分析,其中難免有錯(cuò),更甚者,也許我大錯(cuò)特錯(cuò),壓根就不是這樣的機(jī)制。如果你發(fā)現(xiàn)其中有不正確的地方,請(qǐng)指出,謝謝,在下感激不盡)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
