如何監(jiān)測(cè)內(nèi)存泄漏
如何監(jiān)測(cè)內(nèi)存泄漏
版權(quán)申明
本文可以被自由轉(zhuǎn)載,但是必須遵循如下版權(quán)約定:
1、保留本約定,并保留在文章的開(kāi)頭部分。
2、不能任意修改文章內(nèi)容,或者刪節(jié),增加。如果認(rèn)為本文內(nèi)容有不當(dāng)之處需要修改,請(qǐng)與作者聯(lián)系。
3、不能摘抄本文的內(nèi)容,必須全文發(fā)表或者引用。
4、必須保留作者署名、注明文章出處。(本文授權(quán)給www.linuxaid.com.cn)
5、如不遵守本規(guī)定,則無(wú)權(quán)轉(zhuǎn)載本文。
作者
ariesram
電子郵件地址
ariesram@linuxaid.com.cn, 或 ariesram@may10.ca
本文及本人所有文章均收集在bambi.may10.ca/~ariesram/articles/中。
本文授權(quán)給www.linuxaid.com.cn。
正文:
??? 我曾經(jīng)參與過(guò)一個(gè)比較大的項(xiàng)目,在這個(gè)項(xiàng)目里面,我們沒(méi)有一個(gè)完全確定的設(shè)計(jì)文檔,所以程序的實(shí)現(xiàn)常常變動(dòng)。雖然我們有一個(gè)比較靈活的框架,但是從程序的角度來(lái)講,它使我們的程序非常的混亂。直到發(fā)布的日期臨近,我們還沒(méi)有一個(gè)穩(wěn)定的可以用來(lái)做alpha測(cè)試的版本。所以我們必須盡快的刪除掉無(wú)用的代碼,讓這個(gè)版本足夠的穩(wěn)定。但是,在這個(gè)沒(méi)有足夠規(guī)范的軟件公司,我們沒(méi)有時(shí)間也沒(méi)有足夠的精力來(lái)做邊界測(cè)試之類(lèi)的工作。所以我們只能采用變通的辦法。在軟件中最大的問(wèn)題就是內(nèi)存泄漏。因?yàn)橥霈F(xiàn)這樣的情況,我們?cè)谝欢未a中分配了內(nèi)存,但是卻沒(méi)有釋放它。這造成了很大的問(wèn)題。我們需要一個(gè)簡(jiǎn)單的解決方案,能夠簡(jiǎn)單的編譯進(jìn)這個(gè)項(xiàng)目,在運(yùn)行的時(shí)候,它能夠產(chǎn)生一個(gè)沒(méi)有被釋放的內(nèi)存的列表,用這個(gè)列表,我們能夠改正程序的錯(cuò)誤。這就是我們稱(chēng)之為內(nèi)存跟蹤的方法。首先,我們需要一種代碼,能夠被加入到源代碼中去,而且這種代碼能夠被重用。代碼重用是一種很重要的特性,能夠節(jié)省大量的時(shí)間和金錢(qián)以及程序員的勞動(dòng)。另外,我們的這種代碼必須簡(jiǎn)單,因?yàn)槲覀儺?dāng)時(shí)已經(jīng)沒(méi)有那么多的時(shí)間和精力去完全重看一遍所有的代碼來(lái)重新編寫(xiě)以及改正錯(cuò)誤從而使內(nèi)存跟蹤能夠起作用。
??? 好在,我們總能夠找到解決的辦法。首先,我們檢查了代碼,發(fā)現(xiàn)所有的代碼都是用new來(lái)分配內(nèi)存,用delete來(lái)釋放內(nèi)存。那么,我們能夠用一個(gè)全程替換,來(lái)替換掉所有的new和delete操作符嗎?不能。因?yàn)榇a的規(guī)模太大了,那樣做除了浪費(fèi)時(shí)間沒(méi)有別的任何好處。好在我們的源代碼是用C++來(lái)寫(xiě)成的,所以,這意味著沒(méi)有必要替換掉所有的new和delete,而只用重載這兩個(gè)操作符。對(duì)了,值用重載這兩個(gè)操作符,我們就能在分配和釋放內(nèi)存之前做點(diǎn)什么。這是一個(gè)絕對(duì)的好消息。我們也知道該如何去做。因?yàn)?,MFC也是這么做的。我們需要做的是:跟蹤所有的內(nèi)存分配和交互引用以及內(nèi)存釋放。我們的源代碼使用Visual C++寫(xiě)成,當(dāng)然這種解決方法也可以很輕松的使用在別的C++代碼里面。要做的第一件事情是重載new和delete操作符,它們將會(huì)在所有的代碼中被使用到。我們?cè)趕tdafx.h中,加入:
????? #ifdef _DEBUG
????? inline void * __cdecl operator new(unsigned int size,
???????????????????????????????????????? const char *file, int line)
????? {
????? };
????? inline void __cdecl operator delete(void *p)
????? {
????? };
????? #endif
??? 這樣,我們就重載了new和delete操作符。我們用$ifdef和#endif來(lái)包住這兩個(gè)重載操作符,這樣,這兩個(gè)操作符就不會(huì)在發(fā)布版本中出現(xiàn)。看一看這段代碼,會(huì)發(fā)現(xiàn),new操作符有三個(gè)參數(shù),它們是,分配的內(nèi)存大小,出現(xiàn)的文件名,和行號(hào)。這對(duì)于尋找內(nèi)存泄漏是必需的和重要的。否則,就會(huì)需要很多時(shí)間去尋找它們出現(xiàn)的確切地方。加入了這段代碼,我們的調(diào)用new()的代碼仍然是指向只接受一個(gè)參數(shù)的new操作符,而不是這個(gè)接受三個(gè)參數(shù)的操作符。另外,我們也不想記錄所有的new操作符的語(yǔ)句去包含__FILE__和__LINE__參數(shù)。我們需要做的是自動(dòng)的讓所有的接受一個(gè)參數(shù)的new操作符調(diào)用接受三個(gè)參數(shù)的new操作符。這一點(diǎn)可以用一點(diǎn)點(diǎn)小的技巧去做,例如下面的這一段宏定義,
????? #ifdef _DEBUG
????? #define DEBUG_NEW new(__FILE__, __LINE__)
????? #else
????? #define DEBUG_NEW new
????? #endif
????? #define new DEBUG_NEW
??? 現(xiàn)在我們所有的接受一個(gè)參數(shù)的new操作符都成為了接受三個(gè)參數(shù)的new操作符號(hào),__FILE__和__LINE__被預(yù)編譯器自動(dòng)的插入到其中了。然后,就是作實(shí)際的跟蹤了。我們需要加入一些例程到我們的重載的函數(shù)中去,讓它們能夠完成分配內(nèi)存和釋放內(nèi)存的工作。這樣來(lái)做, #ifdef _DEBUG
????? inline void * __cdecl operator new(unsigned int size,
???????????????????????????????????????? const char *file, int line)
????? {
void *ptr = (void *)malloc(size);
AddTrack((DWORD)ptr, size, file, line);
return(ptr);
????? };
????? inline void __cdecl operator delete(void *p)
????? {
RemoveTrack((DWORD)p);
free(p);
????? };
????? #endif
??? 另外,還需要用相同的方法來(lái)重載new[]和delete[]操作符。這里就省略掉它們了。
??? 最后,我們需要提供一套函數(shù)AddTrack()和RemoveTrack()。我用STL來(lái)維護(hù)存儲(chǔ)內(nèi)存分配記錄的連接表。
這兩個(gè)函數(shù)如下:
????? typedef struct {
DWORD address;
DWORD size;
char file[64];
DWORD line;
????? } ALLOC_INFO;
????? typedef list〈ALLOC_INFO*〉 AllocList;
????? AllocList *allocList;
????? void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum)
????? {
ALLOC_INFO *info;
if(!allocList) {
allocList = new(AllocList);
}
info = new(ALLOC_INFO);
info-〉address = addr;
strncpy(info-〉file, fname, 63);
info-〉line = lnum;
info-〉size = asize;
allocList-〉insert(allocList-〉begin(), info);
????? };
????? void RemoveTrack(DWORD addr)
????? {
AllocList::iterator i;
if(!allocList)
return;
for(i = allocList-〉begin(); i != allocList-〉end(); i++)
{
if((*i)-〉address == addr)
{
allocList-〉remove((*i));
break;
}
}
????? };
??? 現(xiàn)在,在我們的程序退出之前,allocList存儲(chǔ)了沒(méi)有被釋放的內(nèi)存分配。為了看到它們是什么和在哪里被分配的,我們需要打印出allocList中的數(shù)據(jù)。我使用了Visual C++中的Output窗口來(lái)做這件事情。
????? void DumpUnfreed()
????? {
AllocList::iterator i;
DWORD totalSize = 0;
char buf[1024];
if(!allocList)
return;
for(i = allocList-〉begin(); i != allocList-〉end(); i++) {
sprintf(buf, "%-50s: LINE %d, ADDRESS %d %d unfreed ",
(*i)-〉file, (*i)-〉line, (*i)-〉address, (*i)-〉size);
OutputDebugString(buf);
totalSize += (*i)-〉size;
}
sprintf(buf, "----------------------------------------------------------- ");
OutputDebugString(buf);
sprintf(buf, "Total Unfreed: %d bytes ", totalSize);
OutputDebugString(buf);
????? };
??? 現(xiàn)在我們就有了一個(gè)可以重用的代碼,用來(lái)監(jiān)測(cè)跟蹤所有的內(nèi)存泄漏了。這段代碼可以用來(lái)加入到所有的項(xiàng)目中去。雖然它不會(huì)讓你的程序看起來(lái)更好,但是起碼它能夠幫助你檢查錯(cuò)誤,讓程序更加的穩(wěn)定。
非常好我支持^.^
(0) 0%
不好我反對(duì)
(0) 0%
相關(guān)閱讀:
- [電子說(shuō)] 虛擬內(nèi)存和云計(jì)算的關(guān)系 2024-12-04
- [電子說(shuō)] 虛擬內(nèi)存溢出該怎么處理 虛擬內(nèi)存在服務(wù)器中的應(yīng)用 2024-12-04
- [電子說(shuō)] Linux下如何管理虛擬內(nèi)存 使用虛擬內(nèi)存時(shí)的常見(jiàn)問(wèn)題 2024-12-04
- [電子說(shuō)] 虛擬內(nèi)存對(duì)計(jì)算機(jī)性能的影響 2024-12-04
- [電子說(shuō)] 什么是虛擬內(nèi)存分頁(yè) Windows系統(tǒng)虛擬內(nèi)存優(yōu)化方法 2024-12-04
- [電子說(shuō)] 虛擬內(nèi)存不足如何解決 虛擬內(nèi)存和物理內(nèi)存的區(qū)別 2024-12-04
- [電子說(shuō)] 虛擬內(nèi)存的作用和原理 如何調(diào)整虛擬內(nèi)存設(shè)置 2024-12-04
- [電子說(shuō)] 鎧俠將獲上市批準(zhǔn),市值或達(dá)50億美元 2024-12-03
( 發(fā)表人:admin )