内存泄漏定位
今天調(diào)試程序,發(fā)現(xiàn)有內(nèi)存泄漏但是沒有提示具體是哪一行,搞得我很頭疼。
第一種:通過"OutPut窗口"定位引發(fā)內(nèi)存泄漏的代碼。
我們知道,MFC程序如果檢測(cè)到存在內(nèi)存泄漏,退出程序的時(shí)候會(huì)在調(diào)試窗口提醒內(nèi)存泄漏。例如:
class CMyApp : public CWinApp
{
public:
???BOOL InitApplication()
???{
???????int* leak = new int[10];
???????return TRUE;
???}
};
產(chǎn)生的內(nèi)存泄漏報(bào)告大體如下:
Detected memory leaks!
Dumping objects ->
c:\work\test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long.
?Data: <????????????????> CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
這挺好。問題是,如果我們不喜歡MFC,那么難道就沒有辦法?或者自己做?
呵呵,這不需要。其實(shí),MFC也沒有自己做。內(nèi)存泄漏檢測(cè)的工作是VC++的C運(yùn)行庫(kù)做的。也就是說(shuō),只要你是VC++程序員,都可以很方便地檢測(cè)內(nèi)存泄漏。我們還是給個(gè)樣例:
#include <crtdbg.h>
inline void EnableMemLeakCheck()
{
???_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}
void main()
{
???EnableMemLeakCheck();
???int* leak = new int[10];
}
運(yùn)行(提醒:不要按Ctrl+F5,按F5),你將發(fā)現(xiàn),產(chǎn)生的內(nèi)存泄漏報(bào)告與MFC類似,但有細(xì)節(jié)不同,如下:
Detected memory leaks!
Dumping objects ->
{52} normal block at 0x003C4410, 40 bytes long.
?Data: <????????????????> CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
為什么呢?看下面。
定位內(nèi)存泄漏由于哪一句話引起的
你已經(jīng)發(fā)現(xiàn)程序存在內(nèi)存泄漏。現(xiàn)在的問題是,我們要找泄漏的根源。
一般我們首先確定內(nèi)存泄漏是由于哪一句引起。在MFC中,這一點(diǎn)很容易。你雙擊內(nèi)存泄漏報(bào)告的文字,或者在Debug窗口中按F4,IDE就幫你定位到申請(qǐng)?jiān)搩?nèi)存塊的地方。對(duì)于上例,也就是這一句:
int* leak = new int[10];
這多多少少對(duì)你分析內(nèi)存泄漏有點(diǎn)幫助。特別地,如果這個(gè)new僅對(duì)應(yīng)一條delete(或者你把delete漏寫),這將很快可以確認(rèn)問題的癥結(jié)。
我們前面已經(jīng)看到,不使用MFC的時(shí)候,生成的內(nèi)存泄漏報(bào)告與MFC不同,而且你立刻發(fā)現(xiàn)按F4不靈。那么難道MFC做了什么手腳?
其實(shí)不是,我們來(lái)模擬下MFC做的事情。看下例:
inline void EnableMemLeakCheck()
{
???_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}
#ifdef _DEBUG
#define new???new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
void main()
{
???EnableMemLeakCheck();
???int* leak = new int[10];
}
再運(yùn)行這個(gè)樣例,你驚喜地發(fā)現(xiàn),現(xiàn)在內(nèi)存泄漏報(bào)告和MFC沒有任何分別了。
第二種方法:直接定位指定內(nèi)存塊錯(cuò)誤的代碼行(下面轉(zhuǎn))。
單確定了內(nèi)存泄漏發(fā)生在哪一行,有時(shí)候并不足夠。特別是同一個(gè)new對(duì)應(yīng)有多處釋放的情形。在實(shí)際的工程中,以下兩種情況很典型:
創(chuàng)建對(duì)象的地方是一個(gè)類工廠(ClassFactory)模式。很多甚至全部類實(shí)例由同一個(gè)new創(chuàng)建。對(duì)于此,定位到了new出對(duì)象的所在行基本沒有多大幫助。
COM對(duì)象。我們知道COM對(duì)象采用Reference Count維護(hù)生命周期。也就是說(shuō),對(duì)象new的地方只有一個(gè),但是Release的地方很多,你要一個(gè)個(gè)排除。
那么,有什么好辦法,可以迅速定位內(nèi)存泄漏?
答:有。
在內(nèi)存泄漏情況復(fù)雜的時(shí)候,你可以用以下方法定位內(nèi)存泄漏。這是我個(gè)人認(rèn)為通用的內(nèi)存泄漏追蹤方法中最有效的手段。
我們?cè)倩仡^看看crtdbg生成的內(nèi)存泄漏報(bào)告:
Detected memory leaks!
Dumping objects ->
c:\work\test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long.
?Data: <????????????????> CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
除了產(chǎn)生該內(nèi)存泄漏的內(nèi)存分配語(yǔ)句所在的文件名、行號(hào)為,我們注意到有一個(gè)比較陌生的信息:{52}。這個(gè)整數(shù)值代表了什么意思呢?
其實(shí),它代表了第幾次內(nèi)存分配操作。象這個(gè)例子,{52}代表了第52次內(nèi)存分配操作發(fā)生了泄漏。你可能要說(shuō),我只new過一次,怎么會(huì)是第52次?這很容易理解,其他的內(nèi)存申請(qǐng)操作在C的初始化過程調(diào)用的唄。:)
有沒有可能,我們讓程序運(yùn)行到第52次內(nèi)存分配操作的時(shí)候,自動(dòng)停下來(lái),進(jìn)入調(diào)試狀態(tài)?所幸,crtdbg確實(shí)提供了這樣的函數(shù):即 long _CrtSetBreakAlloc(long nAllocID)。我們加上它:
inline void EnableMemLeakCheck()
{
???_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}
#ifdef _DEBUG
#define new???new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
void main()
{
???EnableMemLeakCheck();
???_CrtSetBreakAlloc(52);
???int* leak = new int[10];
}
你發(fā)現(xiàn),程序運(yùn)行到 int* leak = new int[10]; 一句時(shí),自動(dòng)停下來(lái)進(jìn)入調(diào)試狀態(tài)。細(xì)細(xì)體會(huì)一下,你可以發(fā)現(xiàn),這種方式你獲得的信息遠(yuǎn)比在程序退出時(shí)獲得文件名及行號(hào)有價(jià)值得多。因?yàn)閳?bào)告泄漏文件名及行號(hào),你獲得的只是靜態(tài)的信息,然而_CrtSetBreakAlloc則是把整個(gè)現(xiàn)場(chǎng)恢復(fù),你可以通過對(duì)函數(shù)調(diào)用棧分析(我發(fā)現(xiàn)很多人不習(xí)慣看函數(shù)調(diào)用棧,如果你屬于這種情況,我強(qiáng)烈推薦你去補(bǔ)上這一課,因?yàn)樗匾?#xff09;以及其他在線調(diào)試技巧,來(lái)分析產(chǎn)生內(nèi)存泄漏的原因。通常情況下,這種分析方法可以在5分鐘內(nèi)找到肇事者。
當(dāng)然,_CrtSetBreakAlloc要求你的程序執(zhí)行過程是可還原的(多次執(zhí)行過程的內(nèi)存分配順序不會(huì)發(fā)生變化)。這個(gè)假設(shè)在多數(shù)情況下成立。不過,在多線程的情況下,這一點(diǎn)有時(shí)難以保證。
個(gè)人心得:我在用這種方法時(shí)開始沒看懂,后來(lái)在MSDN中也找到了這方面相關(guān)的信息,后來(lái)才會(huì)用。我感覺在這方面網(wǎng)上介紹的不夠詳細(xì),下面我就相對(duì)詳細(xì)地解釋一下(為什么用“相對(duì)詳細(xì)”?本人比較懶)。首先說(shuō)明一下,下面的函數(shù)不需要上面所添加的宏定義和"crtdbg.h"頭文件,也不需要EnableMemLeakCheck()函數(shù)。只需在main函數(shù)一開始運(yùn)行 _CrtSetBreakAlloc(long (4459))函數(shù)。其中4459是申請(qǐng)內(nèi)存的序號(hào)(上面有說(shuō)明),然后F5運(yùn)行(不需要設(shè)斷點(diǎn)),然后會(huì)出現(xiàn)“Find Source”這個(gè)對(duì)話框,點(diǎn)擊“取消”。然后會(huì)出現(xiàn)“User breakpoint called from code at xxxx”的對(duì)話框,點(diǎn)擊“確定”,會(huì)看到一些匯編的代碼(不要怕,其實(shí)我也看不懂,雖然原來(lái)學(xué)過點(diǎn)匯編),調(diào)出堆棧窗口(call stack),在其中的“main() line xxx + xxx bytes”上雙擊(或它的上一行雙擊,我的上一行是一個(gè)自定義函數(shù),雙擊后直接定位到我new的地方,定位還是很準(zhǔn)的,開始我懷疑,但最后檢查果然是這地方?jīng)]釋放)會(huì)定位到錯(cuò)誤行。
第三種:用Ctrl+B來(lái)設(shè)定,不過現(xiàn)在好像忘了。效果根第二種方法基本一樣。
有人會(huì)問,既然第一種方法定位沒問題,為什么還要介紹第二種?其實(shí)在實(shí)際應(yīng)用中,某些內(nèi)存泄漏它沒有定位到哪一行的,只有內(nèi)存塊的序號(hào)(有可能我用的不太會(huì)用),這個(gè)時(shí)候就需要用第二種方法。
【轉(zhuǎn)自】http://www.uml.org.cn/c++/201110272.asp
總結(jié)
- 上一篇: 市巡特警支队政委级别
- 下一篇: 下列操作不属于物理过程的是干燥尿素颗粒吗