記憶體洩漏及檢測
記憶體洩漏概念
維基百科中這樣解釋:在電腦科學中,記憶體洩漏指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體。記憶體洩漏並非指記憶體在物理上的消失,而是應用程式分配某段記憶體後,由於設計錯誤,導致在釋放該段記憶體之前就失去了對該段記憶體的控制,從而造成了記憶體的浪費。
記憶體洩漏通常情況下只能由獲得程式原始碼的程式設計師才能分析出來。因此在一個具有幾十萬行原始碼的專案中尋找記憶體洩露無異於大海撈針,因此對於記憶體洩漏問題不可輕視。
記憶體洩漏的後果
記憶體洩漏會因為減少可用記憶體的數量從而降低計算機的效能。最終,在最糟糕的情況下,過多的可用記憶體被分配掉導致全部或部分裝置停止正常工作,或者應用程式崩潰。
記憶體洩漏帶來的後果可能是不嚴重的,有時甚至能夠被常規的手段檢測出來。在現代作業系統中,一個應用程式使用的常規記憶體在程式終止時被釋放。這表示一個短暫執行的應用程式中的記憶體洩漏不會導致嚴重後果。
在以下情況,記憶體洩漏導致較嚴重的後果:
- 程式執行後置之不理,並且隨著時間的流逝消耗越來越多的記憶體(比如伺服器上的後臺任務,尤其是嵌入式系統中的後臺任務,這些任務可能被執行後很多年內都置之不理);
- 新的記憶體被頻繁地分配,比如當顯示計算機遊戲或動畫視訊畫面時;
- 程式能夠請求未被釋放的記憶體(比如共享記憶體),甚至是在程式終止的時候;
- 洩漏在作業系統內部發生;
- 洩漏在系統關鍵驅動中發生;
- 記憶體非常有限,比如在嵌入式系統或便攜裝置中;
- 當運行於一個終止時記憶體並不自動釋放的作業系統(比如AmigaOS)之上,而且一旦丟失只能通過重啟來恢復。
記憶體洩漏分類
C/C++程式中一般我們關心兩種方面的記憶體洩漏:
- 堆記憶體洩漏(Heap leak)
堆記憶體指的是程式執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊記憶體,用完後必須通過呼叫相應的 free或者delete 刪掉。假設程式的設計錯誤導致這部分記憶體沒有被釋放,那麼以後這部分空間將無法再被使用,就會出現Heap Leak。
- 系統資源洩漏
指程式使用系統分配的資源,比方套接字、檔案描述符、管道等沒有使用對應的函式釋放掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定。
這裡需要注意,指標和它所指向的東西是完全不同的。 區域性變數在函式返回時就會釋放, 但是在指標變數這個問題上, 這表示指標被釋放, 而不是它所指向的物件。 用 malloc() 分配的記憶體直到你明確釋放它之前都會保留在那裡。 一般地, 對於每一個 malloc() (new)都必須有個對應的 free() (delete)呼叫。同時也要注意異常安全問題。
如何檢測記憶體洩漏
- 在linux下記憶體洩漏檢測的檢測(Valgrind(http://valgrind.org/docs/manual/manual.html)
- 在windows下使用第三方工具:VLD工具(http://vld.codeplex.com/)
- 其他工具
檢測記憶體洩露的工具有很多,大家可以根據不同的需要參考官方的說明文件,這裡我就不在贅述了。這裡只簡單介紹最簡單的檢測方法(檢測最簡單的記憶體洩漏問題),讓我們對檢測記憶體洩露的方式和方法有一個基本的認識。
Windows下記憶體洩漏檢測
Windows平臺下面Visual Studio 偵錯程式和 C 執行時 (CRT) 庫為我們提供了檢測和識別記憶體洩漏的有效方法,原理大致如下:記憶體分配要通過CRT在執行時實現,只要在分配記憶體和釋放記憶體時分別做好記錄,程式結束時對比分配記憶體和釋放記憶體的記錄就可以確定是不是有記憶體洩漏。在vs中啟用記憶體檢測的方法如下:
#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include <crtdbg.h>
#include<iostream>
using namespace std;
void GetMemory(char *p, int num)
{
p = (char*)malloc(sizeof(char) * num);//使用new也能夠檢測出來
}
int main()
{
char *str = NULL;
GetMemory(str, 1000);
//while (1) { GetMemory(...); }
//如果main中存在while迴圈呼叫GetMemory,那麼問題將變得很嚴重
cout << "Memory leak test!" << endl;
//加入下面的程式碼來報告記憶體洩漏資訊
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
注:標頭檔案必須保證上面宣告的順序,如果改變了順序,可能不能正常工作。
<crtdbg.h>的_malloc_dbg和_free_dbg將取代標準的malloc和free函數出現在DEBUG版中,它可以跟蹤記憶體的分配和釋放。但是這隻會在DEBUG版本中發生(當#define _DEBUG的時候),而Release版本仍使用標準的malloc和free功能。
#define _CRTDBG_MAP_ALLOC表示使用CRT堆函式的相應的DEBUG版本。這個定義不是必須的,但是沒有它,記憶體洩漏報告含有的只是沒有什麼用處的資訊。
當在偵錯程式下執行程式時,_CrtDumpMemoryLeaks 將在“輸出”視窗中顯示記憶體洩漏資訊。 記憶體洩漏資訊如下所示
- 記憶體分配數值(花括號內)
- 模組的型別(normal、client或者CRT)
- 以十六進位制格式定位的記憶體
- 以位元組計模組的大小
- 第一個十六位元組的內容(也可以用十六進位制)
如果定義了_CRTDBG_MAP_ALLOC,報告的內容還包括出現分配所洩漏記憶體的檔案。在檔名之後括號內的數字是檔案內的行數值。
解釋記憶體模組的型別
記憶體洩漏報告中把每一塊洩漏的記憶體分為普通塊、客戶塊和CRT塊。事實上,你只需要留心普通塊和客戶塊型別。
- 普通塊(normal block):是由你的程式分配的記憶體。
- 客戶塊(client block):是一種特殊的記憶體塊,它是由MFC使用的一個物件,程式退出時,該物件的解構函式沒有被呼叫。MFC new操作符可以用來建立普通塊和客戶塊。
- CRT塊(CRT block):是由C RunTime Library供自己使用而分配的記憶體塊。CRT庫自己來管理這些記憶體的分配與釋放,通常你不會在記憶體洩漏報告中發現有CRT記憶體洩漏,除非程式發生了嚴重的錯誤(例如CRT庫崩潰)。
下面這兩種型別的記憶體塊不會出現在記憶體洩漏報告中:
- 空閒塊(free block):已經被釋放(free)的記憶體塊。
- 忽略塊(Ignore block):是程式設計師顯式宣告過不要在記憶體洩漏報告中出現的記憶體塊
設定CRT報告樣式
通常_CrtDumpMemoryLeaks()會dump記憶體洩漏的資訊到output視窗的Debug欄位。你可以使用_CrtSetReportMode()來重新設定輸出到另一個位置。關於更詳細的如何使用_CrtSetReportMode()說明,請檢視MSDN。
使用_CrtSetDbgFlag
如果你的程式只在一個地方退出,那麼在選擇呼叫_CrtDumpMemoryLeaks的位置是非常容易的。但是,如果你的程式可能會在程式多處位置退出該怎麼辦?如果不希望在每一個可能的出口處呼叫_CrtDumpMemoryLeaks,那麼你可以在你的程式開始處包含下面的呼叫:
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
當程式退出時,將會自動地呼叫_CrtDumpMemoryLeaks(必須設定_CRTDBG_ALLOC_MEM_DF和 _CRTDBG_LEAK_CHECK_DF)。
Linux平臺下的記憶體洩漏檢測
Linux下面也有和Windows下原理相同的方法——mtrace,大家可以參考http://en.wikipedia.org/wiki/Mtrace。我著重介紹一下工具valgrind。演示如下:
測試程式碼:
#include<stdio.h>
#include<malloc.h>
void GetMemory(char *p,int num)
{
p=(char*)malloc(sizeof(char)*num);
}
int main()
{
char *str=NULL;
GetMemory(str,100);
return 0;
}
輸出結果:
由上圖可獲取以下結果:
==2035== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2035== at 0x4C29BC3: malloc (vg_replace_malloc.c:299)
==2035== by 0x400538: GetMemory (in /home/zhitou/linuxsp/a.out)
==2035== by 0x40055F: main (in /home/zhitou/linuxsp/a.out)
是在main中呼叫了GetMemory導致的記憶體洩漏,GetMemory中是呼叫了malloc導致洩漏了100位元組的記憶體。
如何避免記憶體洩露
- 養成良好的編碼規範,申請的記憶體空間記著匹配的去釋放。但是如果碰上異常時,就算注意釋放了,還是可能會出問題。需要下一條智慧指標來管理才有保證;
- 採用RAII思想或者智慧指標來管理資源;
- 有些公司內部規範使用內部實現的私有記憶體管理庫。這套庫自帶記憶體洩漏檢測的功能選項;
- 出問題了使用記憶體洩漏工具檢測。ps:不過很多工具都不夠靠譜,或者收費昂貴;
- 記憶體洩漏非常常見,解決方案分為兩種:
- 事前預防型。如智慧指標等。
- 事後查錯型。如洩漏檢測工具。