C++內存泄露問題定位經驗案例

百度百科:

內存泄漏也稱作存儲滲漏,用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢之後未回收)即所謂內存泄漏。

內存泄漏形象的比喻是操作系統可提供給所有進程的存儲空間正在被某個進程榨乾,最終結果是程序運行時間越長,佔用存儲空間越來越多,最終用盡全部存儲空間,整個系統崩潰。所以內存泄漏是從操作系統的角度來看的。這裏的存儲空間並不是指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁盤交換區設定的大小。由程序申請的一塊內存,如果沒有任何一個指針指向它,那麼這塊內存就泄漏了。

上述描述中,最後一句紅色字體部分存在問題。指針指向的內存,如果沒有管理,也會產生內存泄露。在程序開發過程中,指針所指向的內存沒有正確管理是產生內存問題的最主要原因。

文中部分內容來自網絡,如有不妥,請指正。

1.詳細描述

內存增長過程如下:

wKiom1cgjs3yFvZeAAGHUSrNTwI008.png

wKiom1cgjs_iH8w9AAGUUu9T3Vk779.png

wKioL1cgj5vSAXnEAAFVO9CMhAc492.png

  1. 1.1    檢測方法

  2. 1.1.1.       檢查容器

在實際項目中,程序運行過程內存消耗不斷增加,並不一定是內存泄露造成的。如果代碼量並不是特別大,應該先通讀程序,查找代碼邏輯上是否造成內存問題,特別是STL容器是否clear。在多線程中,部分容器經常作爲全局變量使用,建議仔細檢查相關容器是否需要clear。可以使用.size()函數來檢查容器大小,打印或寫入日誌,查看大小是否持續增加。如:

map<int, int> Intmap;

std::cout << Intmap.size();

  1. 1.1.2.       檢查newmalloc

最好先不要直接使用內存工具進行對位,而是按模塊閱讀代碼,檢查所有newmalloc所分配出來的內存,是否都正確釋放。如:

(1)       int* p = new int

                delete p

                p = NULL

(2)       int* p = new int[10];

                delete[] p

                p = NULL

    malloc同理,不清楚的請尋找相關資料。   

按模塊檢查代碼的好處是能夠讓你自己更理解代碼,特別是你維護的代碼經常是由別人開發實現的。

  1. 1.1.3.       使用庫

Windows下:

Visual Studio 調試器和 C 運行時 (CRT) 庫爲我們提供了檢測和識別內存泄漏的有效方法,原理大致如下:內存分配要通過CRT在運行時實現,只要在分配內存和釋放內存時分別做好記錄,程序結束時對比分配內存和釋放內存的記錄就可以確定是不是有內存泄漏。在vs中啓用內存檢測的方法如下:

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

1

2

3

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

通過包括 crtdbg.h,將 malloc  free 函數映射到它們的調試版本,即 _malloc_dbg  _free_dbg,這兩個函數將跟蹤內存分配和釋放。此映射只在調試版本(在其中定義了_DEBUG)中發生。發佈版本使用普通的 malloc  free 函數。

#define 語句將 CRT 堆函數的基版本映射到對應的“Debug”版本。並非絕對需要該語句;但如果沒有該語句,內存泄漏轉儲包含的有用信息將較少。

STEP2在添加了上述語句之後,可以通過在程序中包括以下語句(通常應恰好放在程序退出位置之前)來轉儲內存泄漏信息:

1

_CrtDumpMemoryLeaks();

此時,完整的代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#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);

}

 

int main(int argc,char** argv)

{

    char *str = NULL;

    GetMemory(str, 100);

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

    _CrtDumpMemoryLeaks();

    return 0;

}

當在調試器下運行程序時,_CrtDumpMemoryLeaks 將在輸出窗口中顯示內存泄漏信息。內存泄漏信息如下所示:wKioL1cgj-ih0OsiAAAZNmc0kK4500.png

如果沒有使用 #define _CRTDBG_MAP_ALLOC 語句,內存泄漏轉儲將如下所示:wKioL1cgkBXAaMjzAAAWgJKgoS0646.png

未定義 _CRTDBG_MAP_ALLOC 時,所顯示的會是:

內存分配編號(在大括號內)。

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

普通塊是由程序分配的普通內存。

客戶端塊是由 MFC 程序用於需要析構函數的對象的特殊類型內存塊。 MFC new 操作根據正在創建的對象的需要創建普通塊或客戶端塊。

“CRT 是由 CRT 庫爲自己使用而分配的內存塊。 CRT 庫處理這些塊的釋放,因此不大可能在內存泄漏報告中看到這些塊,除非出現嚴重錯誤(例如 CRT 庫損壞)。

從不會在內存泄漏信息中看到下面兩種塊類型:

可用塊是已釋放的內存塊。

忽略塊是您已特別標記的塊,因而不出現在內存泄漏報告中。

十六進制形式的內存位置。

以字節爲單位的塊大小。

16 字節的內容(亦爲十六進制)。

定義了 _CRTDBG_MAP_ALLOC 時,還會顯示在其中分配泄漏的內存的文件。文件名後括號中的數字(本示例中爲 10)是該文件中的行號。

注意:如果程序總是在同一位置退出,調用 _CrtDumpMemoryLeaks 將非常容易。如果程序從多個位置退出,則無需在每個可能退出的位置放置對_CrtDumpMemoryLeaks 的調用,而可以在程序開始處包含以下調用:

1

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

該語句在程序退出時自動調用 _CrtDumpMemoryLeaks必須同時設置 _CRTDBG_ALLOC_MEM_DF  _CRTDBG_LEAK_CHECK_DF 兩個位域,如前面所示。

定位具體的內存泄漏地方:

通過上面的方法,我們幾乎可以定位到是哪個地方調用內存分配函數mallocnew等,如上例中的GetMemory函數中,即第10行!但是不能定位到,在哪個地方調用GetMemory()導致的內存泄漏,而且在大型項目中可能有很多處調用GetMemory。如何要定位到在哪個地方調用GetMemory導致的內存泄漏?

定位內存泄漏的另一種技術涉及在關鍵點對應用程序的內存狀態拍快照。 CRT 庫提供一種結構類型 _CrtMemState,您可用它存儲內存狀態的快照:

1

_CrtMemState s1, s2, s3;

若要在給定點對內存狀態拍快照,請向 _CrtMemCheckpoint 函數傳遞 _CrtMemState 結構。該函數用當前內存狀態的快照填充此結構:

1

_CrtMemCheckpoint( &s1 );

通過向 _CrtMemDumpStatistics 函數傳遞 _CrtMemState 結構,可以在任意點轉儲該結構的內容:

1

_CrtMemDumpStatistics( &s1 );

若要確定代碼中某一部分是否發生了內存泄漏,可以在該部分之前和之後對內存狀態拍快照,然後使用 _CrtMemDifference 比較這兩個狀態:

1

2

3

4

5

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

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

   _CrtMemDumpStatistics( &s3 );

顧名思義,_CrtMemDifference 比較兩個內存狀態(s1 s2),生成這兩個狀態之間差異的結果(s3)。在程序的開始和結尾放置 _CrtMemCheckpoint 調用,並使用_CrtMemDifference 比較結果,是檢查內存泄漏的另一種方法。如果檢測到泄漏,則可以使用 _CrtMemCheckpoint 調用通過二進制搜索技術來劃分程序和定位泄漏。

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

#include <iostream>

using namespace std;

_CrtMemState s1, s2, s3;

void GetMemory(char *p, int num)

{

    p = (char*)malloc(sizeof(char) * num);

}

int main(int argc,char** argv)

{

    _CrtMemCheckpoint( &s1 );

    char *str = NULL;

    GetMemory(str, 100);

    _CrtMemCheckpoint( &s2 );

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

        _CrtMemDumpStatistics( &s3 );

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

    _CrtDumpMemoryLeaks();

    return 0;

}

調試時,程序輸出如下結果:wKioL1cgkH3gsa6jAAAgrX1hqnQ765.png

這說明在s1s2之間存在內存泄漏!!!如果GetMemory不是在s1s2之間調用,那麼就不會有信息輸出。

Linux下內存檢查:

在上面我們介紹了,vs中在代碼中包含crtdbg.h,將 malloc  free 函數映射到它們的調試版本,即 _malloc_dbg  _free_dbg,這兩個函數將跟蹤內存分配和釋放。此映射只在調試版本(在其中定義了_DEBUG)中發生。發佈版本使用普通的 malloc  free 函數。即爲mallocfree做了鉤子,用於記錄內存分配信息。

#include <stdlib.h>

#include <mcheck.h>

 

int main(void) {

 

         mtrace(); /* Starts the recording of memory allocations and releases */

 

         int* a = NULL;

 

         a = malloc(sizeof(int)); /* allocate memory and assign it to the pointer */

 

         if (a == NULL) {

                   return 1; /* error */

         }

 

         free(a); /* we free the memory we allocated so we don't have leaks */

 

         muntrace();

 

         return 0; /* exit */

}

詳細內容請參考http://en.wikipedia.org/wiki/Mtrace

  1. 1.1.4.        使用工具

Windows下:Visual Leak Detector

    VLD網址:http://vld.codeplex.com/

    http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio下載Visual Leak Detector,打開Visual C++ IDE"工具"→"選項"→"項目和解決方案"→"VC++ 目錄",在"包含文件"中增加VLD的頭文件路徑"\include"路徑,在"庫文件"增加VLD庫文件的"\lib\Win32"路徑,此外動態庫的"\bin\Win32"路徑在安裝時已經添加到環境變量裏面了,若是未添加,則需要手動拷貝"\bin\Win32"下的文件到可執行文件所在的目錄中(拷貝的文件有dbghelp.dll/Microsoft.DTfW.DHL.manifest/vld_x86.dll/vld.ini)。

接下來需要將VLD加入到自己的代碼中。方法很簡單,只要在包含入口函數的.cpp文件中包含vld.h就可以。如果這個cpp文件中包含了stdafx.h,則將包含vld.h的語句放在stdafx.h的包含語句之後,否則放在最前面。

    示例程序:

#include<vld.h>                 // 包含VLD的頭文件

#include<stdlib.h>

#include<stdio.h>

void f()

{

    int *p = new int(0x12345678);

    printf("p=%08x, ", p);

}

int main()

{

    f();

    return 0;

}

    注:VLD只能在Windows下使用,在包含vld.h頭文件時增加預編譯選項。

    注:在Release模式下,不會鏈接VisualLeak Detector

    注:Visual LeakDetector有一些配置項,可以設置內存泄露報告的保存地(文件、調試器),拷貝"\Visual Leak Detector"路徑下的vld.ini文件到執行文件所在的目錄下(在IDE運行的話,則需要拷貝到工程目錄下),修改以下項:

ReportFile =.\memory_leak_report.txt

ReportTo = both

Linux下:valgrind

命令:yum install valgrind進行安裝。

/usr/include/gnu/stubs.h:7:27: error: gnu/stubs-32.h: No such file or directory

make: *** [TrafficInfo.o] Error 1

是缺少需要的程序包解決辦法:安裝缺少的包:

 yum install glibc-devel

命令:valgrind --tool=memcheck ./TestPRTI即可對TestPRTI程序進行內存檢測。

wKiom1cgkFySawEaAADVPHGhr4c525.png

如上圖所示知道:

==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中調用了GetMemory導致的內存泄漏,GetMemory中是調用了malloc導致泄漏了100字節的內存

詳細內容請參考

http://blog.csdn.net/dndxhej/article/details/7855520

http://www.linuxidc.com/Linux/2014-10/108087.htm

   

  1. 1.2    智能指針

智能指針便可以有效緩解這類問題,但並不意味着使用智能指針就不再會產生內存泄露問題。包括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr

對於編譯器來說,智能指針實際上是一個棧對象,並非指針類型,在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。所有智能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。

具體使用方法詳見http://blog.csdn.net/xt_xiaotian/article/details/5714477

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章