C/C++內(nèi)存泄漏及檢測(cè)

“該死系統(tǒng)存在內(nèi)存泄漏問題”投慈,項(xiàng)目中由于各方面因素圆丹,總是有人抱怨存在內(nèi)存泄漏,系統(tǒng)長時(shí)間運(yùn)行之后交煞,可用內(nèi)存越來越少坊夫,甚至導(dǎo)致了某些服務(wù)失敗砖第。內(nèi)存泄漏是最難發(fā)現(xiàn)的常見錯(cuò)誤之一,因?yàn)槌怯猛陜?nèi)存或調(diào)用malloc失敗环凿,否則都不會(huì)導(dǎo)致任何問題梧兼。實(shí)際上,使用C/C++這類沒有垃圾回收機(jī)制的語言時(shí)智听,你很多時(shí)間都花在處理如何正確釋放內(nèi)存上袱院。如果程序運(yùn)行時(shí)間足夠長,如后臺(tái)進(jìn)程運(yùn)行在服務(wù)器上瞭稼,只要服務(wù)器不宕機(jī)就一直運(yùn)行忽洛,一個(gè)小小的失誤也會(huì)對(duì)程序造成重大的影響,如造成某些關(guān)鍵服務(wù)失敗环肘。

對(duì)于內(nèi)存泄漏欲虚,本人深有體會(huì)!實(shí)習(xí)的時(shí)候悔雹,公司一個(gè)項(xiàng)目中就存在內(nèi)存泄漏問題复哆,項(xiàng)目的代碼兩非常大,后臺(tái)進(jìn)程也比較多腌零,造成內(nèi)存泄漏的地方比較難找梯找。這次機(jī)會(huì)是我對(duì)如何查找內(nèi)存泄漏問題,有了一定的經(jīng)驗(yàn)益涧,后面自己的做了相關(guān)實(shí)驗(yàn)锈锤,在此我分享一下內(nèi)存泄漏如何調(diào)試查找闲询,主要內(nèi)容如下:

1阎姥、內(nèi)存泄漏簡(jiǎn)介

2呼巴、Windows平臺(tái)下的內(nèi)存泄漏檢測(cè)

2.1、檢測(cè)是否存在內(nèi)存泄漏問題

2.2诊赊、定位具體的內(nèi)存泄漏地方

3屑埋、Linux平臺(tái)下的內(nèi)存泄漏檢測(cè)

4痰滋、總結(jié)

其實(shí)Windows敲街、Linux下面的內(nèi)存檢測(cè)都可以單獨(dú)開篇詳細(xì)介紹逻恐,方法和工具也遠(yuǎn)遠(yuǎn)不止文中介紹到的,我的方法也不是最優(yōu)的,如果您有更好的方法骨饿,也請(qǐng)您告訴我和大家亏栈。

1察署、內(nèi)存泄漏簡(jiǎn)介及后果

wikipedia中這樣定義內(nèi)存泄漏:在計(jì)算機(jī)科學(xué)中嘶是,內(nèi)存泄漏指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況辖源。內(nèi)存泄漏并非指內(nèi)存在物理上的消失矾湃,而是應(yīng)用程序分配某段內(nèi)存后邀跃,由于設(shè)計(jì)錯(cuò)誤,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制蒜茴,從而造成了內(nèi)存的浪費(fèi)崔泵。

最難捉摸也最難檢測(cè)到的錯(cuò)誤之一是內(nèi)存泄漏,即未能正確釋放以前分配的內(nèi)存的 bug猪瞬。 只發(fā)生一次的小的內(nèi)存泄漏可能不會(huì)被注意憎瘸,但泄漏大量內(nèi)存的程序或泄漏日益增多的程序可能會(huì)表現(xiàn)出各種征兆:從性能不良(并且逐漸降低)到內(nèi)存完全用盡。 更糟的是陈瘦,泄漏的程序可能會(huì)用掉太多內(nèi)存幌甘,以致另一個(gè)程序失敗,而使用戶無從查找問題的真正根源痊项。 此外锅风,即使無害的內(nèi)存泄漏也可能是其他問題的征兆。

內(nèi)存泄漏會(huì)因?yàn)闇p少可用內(nèi)存的數(shù)量從而降低計(jì)算機(jī)的性能鞍泉。最終皱埠,在最糟糕的情況下,過多的可用內(nèi)存被分配掉導(dǎo)致全部或部分設(shè)備停止正常工作咖驮,或者應(yīng)用程序崩潰边器。內(nèi)存泄漏可能不嚴(yán)重训枢,甚至能夠被常規(guī)的手段檢測(cè)出來。在現(xiàn)代操作系統(tǒng)中忘巧,一個(gè)應(yīng)用程序使用的常規(guī)內(nèi)存在程序終止時(shí)被釋放恒界。這表示一個(gè)短暫運(yùn)行的應(yīng)用程序中的內(nèi)存泄漏不會(huì)導(dǎo)致嚴(yán)重后果。

在以下情況砚嘴,內(nèi)存泄漏導(dǎo)致較嚴(yán)重的后果:

程序運(yùn)行后置之不理十酣,并且隨著時(shí)間的流失消耗越來越多的內(nèi)存(比如服務(wù)器上的后臺(tái)任務(wù),尤其是嵌入式系統(tǒng)中的后臺(tái)任務(wù)际长,這些任務(wù)可能被運(yùn)行后很多年內(nèi)都置之不理)耸采;

新的內(nèi)存被頻繁地分配,比如當(dāng)顯示電腦游戲或動(dòng)畫視頻畫面時(shí)也颤;

程序能夠請(qǐng)求未被釋放的內(nèi)存(比如共享內(nèi)存)洋幻,甚至是在程序終止的時(shí)候郁轻;

泄漏在操作系統(tǒng)內(nèi)部發(fā)生翅娶;

泄漏在系統(tǒng)關(guān)鍵驅(qū)動(dòng)中發(fā)生;

內(nèi)存非常有限好唯,比如在嵌入式系統(tǒng)或便攜設(shè)備中竭沫;

當(dāng)運(yùn)行于一個(gè)終止時(shí)內(nèi)存并不自動(dòng)釋放的操作系統(tǒng)(比如AmigaOS)之上,而且一旦丟失只能通過重啟來恢復(fù)骑篙。

下面我們通過以下例子來介紹如何檢測(cè)內(nèi)存泄漏問題:

#include <stdlib.h>

#include <iostream>

using namespace std;

void GetMemory(char?*p,?int?num)

{

p = (char*)malloc(sizeof(char) * num);//使用new也能夠檢測(cè)出來

}

int?main(int?argc,char** argv)

{

char?*str = NULL;

GetMemory(str, 100);

cout<<"Memory leak test!"<<endl;

//如果main中存在while循環(huán)調(diào)用GetMemory

//那么問題將變得很嚴(yán)重

//while(1){GetMemory(...);}

return 0;

}

實(shí)際中不可能這么簡(jiǎn)單蜕提,如果這么簡(jiǎn)單也用不著別的方法,程序員一眼就可以看出問題靶端,此程序只用于測(cè)試谎势。

2、Windows平臺(tái)下的內(nèi)存泄漏檢測(cè)

2.1杨名、檢測(cè)是否存在內(nèi)存泄漏問題

Windows平臺(tái)下面Visual Studio 調(diào)試器和 C 運(yùn)行時(shí) (CRT) 庫為我們提供了檢測(cè)和識(shí)別內(nèi)存泄漏的有效方法脏榆,原理大致如下:內(nèi)存分配要通過CRT在運(yùn)行時(shí)實(shí)現(xiàn),只要在分配內(nèi)存和釋放內(nèi)存時(shí)分別做好記錄台谍,程序結(jié)束時(shí)對(duì)比分配內(nèi)存和釋放內(nèi)存的記錄就可以確定是不是有內(nèi)存泄漏须喂。在vs中啟用內(nèi)存檢測(cè)的方法如下:

STEP1,在程序中包括以下語句: (#include 語句必須采用上文所示順序趁蕊。 如果更改了順序坞生,所使用的函數(shù)可能無法正常工作。)

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

通過包括 crtdbg.h掷伙,將 malloc 和 free 函數(shù)映射到它們的調(diào)試版本是己,即 _malloc_dbg 和 _free_dbg,這兩個(gè)函數(shù)將跟蹤內(nèi)存分配和釋放任柜。 此映射只在調(diào)試版本(在其中定義了_DEBUG)中發(fā)生卒废。 發(fā)布版本使用普通的?malloc?和?free?函數(shù)寒波。

#define 語句將 CRT 堆函數(shù)的基版本映射到對(duì)應(yīng)的“Debug”版本。 并非絕對(duì)需要該語句升熊;但如果沒有該語句俄烁,內(nèi)存泄漏轉(zhuǎn)儲(chǔ)包含的有用信息將較少。

STEP2级野, 在添加了上述語句之后页屠,可以通過在程序中包括以下語句(通常應(yīng)恰好放在程序退出位置之前)來轉(zhuǎn)儲(chǔ)內(nèi)存泄漏信息:

_CrtDumpMemoryLeaks();

此時(shí),完整的代碼如下:

+ View Code

當(dāng)在調(diào)試器下運(yùn)行程序時(shí)蓖柔,_CrtDumpMemoryLeaks 將在“輸出”窗口中顯示內(nèi)存泄漏信息辰企。 內(nèi)存泄漏信息如下所示:

如果沒有使用 #define _CRTDBG_MAP_ALLOC 語句,內(nèi)存泄漏轉(zhuǎn)儲(chǔ)將如下所示:

未定義 _CRTDBG_MAP_ALLOC 時(shí)况鸣,所顯示的會(huì)是:

內(nèi)存分配編號(hào)(在大括號(hào)內(nèi))牢贸。

塊類型(普通、客戶端或 CRT)镐捧。

“普通塊”是由程序分配的普通內(nèi)存潜索。

“客戶端塊”是由 MFC 程序用于需要析構(gòu)函數(shù)的對(duì)象的特殊類型內(nèi)存塊。 MFC new 操作根據(jù)正在創(chuàng)建的對(duì)象的需要?jiǎng)?chuàng)建普通塊或客戶端塊懂酱。

“CRT 塊”是由 CRT 庫為自己使用而分配的內(nèi)存塊竹习。 CRT 庫處理這些塊的釋放,因此您不大可能在內(nèi)存泄漏報(bào)告中看到這些塊列牺,除非出現(xiàn)嚴(yán)重錯(cuò)誤(例如 CRT 庫損壞)整陌。

從不會(huì)在內(nèi)存泄漏信息中看到下面兩種塊類型:

“可用塊”是已釋放的內(nèi)存塊。

“忽略塊”是您已特別標(biāo)記的塊瞎领,因而不出現(xiàn)在內(nèi)存泄漏報(bào)告中泌辫。

十六進(jìn)制形式的內(nèi)存位置。

以字節(jié)為單位的塊大小九默。

前 16 字節(jié)的內(nèi)容(亦為十六進(jìn)制)震放。

定義了 _CRTDBG_MAP_ALLOC 時(shí),還會(huì)顯示在其中分配泄漏的內(nèi)存的文件荤西。 文件名后括號(hào)中的數(shù)字(本示例中為 10)是該文件中的行號(hào)澜搅。

注意:如果程序總是在同一位置退出,調(diào)用 _CrtDumpMemoryLeaks 將非常容易邪锌。 如果程序從多個(gè)位置退出勉躺,則無需在每個(gè)可能退出的位置放置對(duì)_CrtDumpMemoryLeaks?的調(diào)用,而可以在程序開始處包含以下調(diào)用:

+ View Code

該語句在程序退出時(shí)自動(dòng)調(diào)用?_CrtDumpMemoryLeaks觅丰。 必須同時(shí)設(shè)置?_CRTDBG_ALLOC_MEM_DF?和_CRTDBG_LEAK_CHECK_DF?兩個(gè)位域饵溅,如前面所示。

2.2妇萄、定位具體的內(nèi)存泄漏地方

通過上面的方法蜕企,我們幾乎可以定位到是哪個(gè)地方調(diào)用內(nèi)存分配函數(shù)malloc和new等咬荷,如上例中的GetMemory函數(shù)中,即第10行轻掩!但是不能定位到幸乒,在哪個(gè)地方調(diào)用GetMemory()導(dǎo)致的內(nèi)存泄漏,而且在大型項(xiàng)目中可能有很多處調(diào)用GetMemory唇牧。如何要定位到在哪個(gè)地方調(diào)用GetMemory導(dǎo)致的內(nèi)存泄漏罕扎?

定位內(nèi)存泄漏的另一種技術(shù)涉及在關(guān)鍵點(diǎn)對(duì)應(yīng)用程序的內(nèi)存狀態(tài)拍快照。 CRT 庫提供一種結(jié)構(gòu)類型?_CrtMemState丐重,您可用它存儲(chǔ)內(nèi)存狀態(tài)的快照:

_CrtMemState s1, s2, s3;

若要在給定點(diǎn)對(duì)內(nèi)存狀態(tài)拍快照腔召,請(qǐng)向 _CrtMemCheckpoint 函數(shù)傳遞?_CrtMemState?結(jié)構(gòu)。 該函數(shù)用當(dāng)前內(nèi)存狀態(tài)的快照填充此結(jié)構(gòu):

_CrtMemCheckpoint( &s1 );

通過向 _CrtMemDumpStatistics 函數(shù)傳遞?_CrtMemState?結(jié)構(gòu)扮惦,可以在任意點(diǎn)轉(zhuǎn)儲(chǔ)該結(jié)構(gòu)的內(nèi)容:

_CrtMemDumpStatistics( &s1 );

若要確定代碼中某一部分是否發(fā)生了內(nèi)存泄漏臀蛛,可以在該部分之前和之后對(duì)內(nèi)存狀態(tài)拍快照,然后使用 _CrtMemDifference 比較這兩個(gè)狀態(tài):

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )

_CrtMemDumpStatistics( &s3 );

顧名思義崖蜜,_CrtMemDifference?比較兩個(gè)內(nèi)存狀態(tài)(s1 和 s2)浊仆,生成這兩個(gè)狀態(tài)之間差異的結(jié)果(s3)。 在程序的開始和結(jié)尾放置?_CrtMemCheckpoint?調(diào)用纳猪,并使用_CrtMemDifference?比較結(jié)果氧卧,是檢查內(nèi)存泄漏的另一種方法桃笙。 如果檢測(cè)到泄漏氏堤,則可以使用?_CrtMemCheckpoint?調(diào)用通過二進(jìn)制搜索技術(shù)來劃分程序和定位泄漏。

如上面的例子程序我們可以這樣來定位確切的調(diào)用GetMemory的地方:

+ View Code

調(diào)試時(shí)搏明,程序輸出如下結(jié)果:

這說明在s1和s2之間存在內(nèi)存泄漏J笮狻!星著!如果GetMemory不是在s1和s2之間調(diào)用购笆,那么就不會(huì)有信息輸出。

3虚循、Linux平臺(tái)下的內(nèi)存泄漏檢測(cè)

在上面我們介紹了同欠,vs中在代碼中“包含crtdbg.h,將 malloc 和 free 函數(shù)映射到它們的調(diào)試版本横缔,即 _malloc_dbg 和 _free_dbg铺遂,這兩個(gè)函數(shù)將跟蹤內(nèi)存分配和釋放。 此映射只在調(diào)試版本(在其中定義了_DEBUG)中發(fā)生茎刚。 發(fā)布版本使用普通的?malloc?和?free?函數(shù)襟锐。”即為malloc和free做了鉤子膛锭,用于記錄內(nèi)存分配信息粮坞。

Linux下面也有原理相同的方法——mtrace蚊荣,http://en.wikipedia.org/wiki/Mtrace。方法類似莫杈,我這就不具體描述互例,參加給出的鏈接。這節(jié)我主要介紹一個(gè)非常強(qiáng)大的工具valgrind筝闹。如下圖所示:

如上圖所示知道:

==6118== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1

==6118== at 0x4024F20: malloc (vg_replace_malloc.c:236)

==6118== by 0x8048724: GetMemory(char*, int) (in /home/netsky/workspace/a.out)

==6118== by 0x804874E: main (in /home/netsky/workspace/a.out)

是在main中調(diào)用了GetMemory導(dǎo)致的內(nèi)存泄漏敲霍,GetMemory中是調(diào)用了malloc導(dǎo)致泄漏了100字節(jié)的內(nèi)存。

Things to notice:

? There is a lot of information in each error message; read it carefully.

? The 6118 is the process ID; it’s usually unimportant.

? The ?rst line ("Heap Summary") tells you what kind of error it is.

? Below the ?rst line is a stack trace telling you where the problem occurred. Stack traces can get quite large, and be

confusing, especially if you are using the C++ STL. Reading them from the bottom up can help.

? The code addresses (eg. 0x4024F20) are usually unimportant, but occasionally crucial for tracking down weirder

bugs.

The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked,

unfortunately. (Ignore the "vg_replace_malloc.c", that’s an implementation detail.)

There are several kinds of leaks; the two most important categories are:

? "de?nitely lost": your program is leaking memory -- ?x it!

? "probably lost": your program is leaking memory, unless you’re doing funny things with pointers (such as moving

them to point to the middle of a heap block)

Valgrind的使用請(qǐng)見手冊(cè)http://valgrind.org/docs/manual/manual.html丁存。

4肩杈、總結(jié)

其實(shí)內(nèi)存泄漏的原因可以概括為:調(diào)用了malloc/new等內(nèi)存申請(qǐng)的操作,但缺少了對(duì)應(yīng)的free/delete解寝,總之就是扩然,malloc/new比free/delete的數(shù)量多。我們?cè)诰幊虝r(shí)需要注意這點(diǎn)聋伦,保證每個(gè)malloc都有對(duì)應(yīng)的free夫偶,每個(gè)new都有對(duì)應(yīng)的deleted!>踉觥兵拢!平時(shí)要養(yǎng)成這樣一個(gè)好的習(xí)慣。

要避免內(nèi)存泄漏可以總結(jié)為以下幾點(diǎn):

程序員要養(yǎng)成良好習(xí)慣逾礁,保證malloc/new和free/delete匹配说铃;

檢測(cè)內(nèi)存泄漏的關(guān)鍵原理就是,檢查malloc/new和free/delete是否匹配嘹履,一些工具也就是這個(gè)原理腻扇。要做到這點(diǎn),就是利用宏或者鉤子砾嫉,在用戶程序與運(yùn)行庫之間加了一層幼苛,用于記錄內(nèi)存分配情況。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焕刮,一起剝皮案震驚了整個(gè)濱河市舶沿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌配并,老刑警劉巖括荡,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荐绝,居然都是意外死亡一汽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來召夹,“玉大人岩喷,你說我怎么就攤上這事〖嘣鳎” “怎么了纱意?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鲸阔。 經(jīng)常有香客問我偷霉,道長,這世上最難降的妖魔是什么褐筛? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任类少,我火速辦了婚禮,結(jié)果婚禮上渔扎,老公的妹妹穿的比我還像新娘硫狞。我一直安慰自己,他們只是感情好晃痴,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布残吩。 她就那樣靜靜地躺著,像睡著了一般倘核。 火紅的嫁衣襯著肌膚如雪泣侮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天紧唱,我揣著相機(jī)與錄音活尊,去河邊找鬼。 笑死琼蚯,一個(gè)胖子當(dāng)著我的面吹牛酬凳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遭庶,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼稠屠!你這毒婦竟也來了峦睡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤权埠,失蹤者是張志新(化名)和其女友劉穎榨了,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攘蔽,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡龙屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片转捕。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡作岖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出五芝,到底是詐尸還是另有隱情痘儡,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布枢步,位于F島的核電站沉删,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏醉途。R本人自食惡果不足惜矾瑰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隘擎。 院中可真熱鬧脯倚,春花似錦、人聲如沸嵌屎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宝惰。三九已至植榕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尼夺,已是汗流浹背尊残。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淤堵,地道東北人寝衫。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像拐邪,于是被迫代替她去往敵國和親慰毅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容