C/C++應用程序內存泄漏檢查統計方案

  1、前緒

  C/C++程序給某些程序員的幾大印象之一就是內存本身管理容易泄漏容易崩,筆者曾經在一個產品中使用C語言開發維護部分模塊,只要產品有內存泄漏和崩潰的問題,就被甩鍋「個人程序是C#開發的內存都是託管的,C++那邊也沒有內存(庇護其好友),確定是C這邊的問題」(話說一個十幾年的程序員還停留在語言層面不以爲有點low嗎),筆者畢業不到一年,聽到此語內心一萬頭草泥馬奔騰而過,默默地修改了程序,注意不是修改bug(哈哈),而是把全部malloc和free都替換成了自定義宏MALLOC和FREE,debug版本全部內存分配釋放都打了日誌,程序結束自動報告相似「Core Memory Leaks: 字節數」,此後內存泄漏的問題再也沒人敢甩過來了。語言僅僅是個工具,人心是大道。程序員

  2、C程序內存泄漏檢測方案參考

  C語言應用程序通常使用malloc和free兩個函數分配和釋放內存,對它們作內存泄漏檢測仍是很好想到完美方案的。所謂的完美:1)當內存泄漏時能迅速定位到是哪一行代碼分配的;2)使用簡單與原先無異;3)release時或者不須要調試內存的時候,仍然使用原生態函數,不影響效率。函數

 1 #ifdef DEBUG_MEMORY
 2 #define MALLOC MallocDebug(__FILE__, __LINE__, size)
 3 #define FREE FreeDebug(__FILE__, __LINE__, p)
 4 #else
 5 #define MALLOC malloc
 6 #define FREE free
 7 #endif
 8 
 9 #ifdef DEBUG_MEMORY
10 #define MEM_OP_MALLOC 1
11 #define MEM_OP_FREE 0
12 
13 void LogMemory(const char* file, int line, void* p, int operation, size_t size);
14 
15 void* MallocDebug(const char* file, int line, size_t size)
16 {
17     void* p = malloc(size);
18     LogMemory(file, line, p, MEM_OP_MALLOC, size);
19     return p;
20 }
21 
22 void FreeDebug(const char* file, int line, void* p)
23 {
24     LogMemory(file, line, p, MEM_OP_FREE, 0);
25      free(p);     
26 }    
27 
28 void LogMemory(const char* file, int line, void* p, int operation, size_t size)
29 {
30     //打印日誌(malloc/free、指針、文件名、行號、指針、第幾回分配的序號),分配序號能夠實現相似與crtdbg的CrtSetBreakAlloc函數的功能
31     //操做爲malloc時,向map插入一條記錄,增長內存使用大小;
32     //操做爲free時,在map中找到記錄並刪除,減小內存使用大小。
33 }
34 
35 void DetectMemoryLeaks()
36 {
37     //打印當前內存管理的map中剩餘的沒有釋放的內存指針、文件名、行號、大小、分配序號
38 }
39 
40 #endif
41 
42 void Program()
43 {
44     int *pArray = MALLOC(sizeof(int) * 10);
45     FREE(pArray);
46 
47 #ifdef DEBUG_MEMORY
48     DetectMemoryLeaks();
49 #endif
50 }

    C語言應用程序中的上述內存泄漏檢測方案至此完美收官,記錄分配序號,也能夠向CrtSetBreakAlloc那樣調試內存泄漏哦。工具

    3、C++程序內存泄漏檢測方案參考

    近期在跟蹤C++項目的內存泄漏,項目包含多個工程(1個exe+多個自開發dll+多個第三方dll)。spa

    1.首先考慮的第一個方案是利用crtdbg。踩得第一個坑是記得看下工程配置運行時庫選項用debug版本(/MTd或/MDd),不然無效。非MFC程序報不出可疑泄漏內存的文件名及行號,要在整個程序全部使用new的文件中包含"#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)"的宏定義。對於單個工程程序而言調試比較簡單方便;對於多個dll尤爲是有第三方庫時,/MTd配置下要很是當心,/MDd配置要好不少,但實際中使用crtdbg調試仍是偶爾會崩在系統底層內存分配的地方,出現的問題不在我的解決能力以內,放棄了。debug

    2.其次的第二個方案,考慮本身重載operator new和operator delete,固然是要重載operator new(size_t size, const char* file, int line)這個版本才能在泄漏時定位到行號。一樣也是要全部使用new的文件中包含"#define new new( __FILE__, __LINE__)"的宏定義。問題是雖然能夠重載operator delete(void* p,  const char* file, int line)這個版本,可是這個版本只會在placement new失敗時纔會調用,正常時候仍是調用的operator delete(void* p)版本,因此還須要重載operator delete(void* p)版本,問題是沒有重載的系統內置的operator new(size_t size)版本分配的全部內存也會走用戶重載後的operator delete(void* p)版本,不配對,一塊兒把operator new(size_t size)也重載了。    指針

      第二個方案的另一個問題是程序要包含宏"#define new new( __FILE__, __LINE__)",但第三方庫頭文件中有placement new的用法new(pointer)classA(),項目大一點頭文件順序很差調,編譯失敗。還有就是這個方案實踐中(多dll所有設置的相同的運行時庫配置)也在系統底層分配內存的方法崩潰過,也多是我的在哪裏的處理有問題,總之再也不考慮前兩個方案了,打算在應用層作處理。調試

    3.最後肯定在最上層想方案,首先C++不能自定義操做符,不然就能定義一個操做符A* pA = debugnew A(1, 2)了。宏不能有空格只能考慮函數debugnew(A, 1, 2)了。下面上方案。日誌

    全部要分配或釋放內存的文件中包含DebugMemory.h頭文件(僞代碼):code

 1 //文件名:DebugMemory.h
 2 
 3 #ifdef DEBUG_MEMORY
 4 #define NEW(T, ...) DebugNew<T>(__FILE__, __LINE__, __VA_ARGS__)
 5 #define DEL(p) DebugDelete(__FILE__, __LINE__, p)
 6 #define NEW_ARRAY(T, size) DebugNewArray<T>(__FILE__, __LINE__, size)
 7 #define DEL_ARRAY(p) DebugDeleteArray(__FILE__, __LINE__, p)
 8 #else
 9 #define NEW(T, ...) new T(__VA_ARGS__)
10 #define DEL(p) delete(p)
11 #define NEW_ARRAY(T, size) new T[size]
12 #define DEL_ARRAY(p) delete[] p
13 #endif
14 
15 #ifdef DEBUG_MEMORY
16 
17 template<class T, class... Args>
18 T* DebugNew(const char* file, int line, Args&&... args)
19 {
20     T* p = new T(std::forward<Args>(args)...);
21     //todo:記錄操做(new)、指針、文件、行號、分配號
22     return p;
23 }
24 
25 template<class T>
26 void DebugDelete(const char* file, int line, T* p)
27 {
28     //todo:記錄操做(delete)、指針、文件、行號
29     delete p;
30 }
31 
32 template<class T>
33 T* DebugNewArray(const char* file, int line, size_t size)
34 {
35     T* p = new T[size];
36     //todo:記錄操做(new[])、指針、文件、行號、分配號
37     return p;
38 }
39 
40 template<class T>
41 void DebugDeleteArray(const char* file, int line, T* p)
42 {
43     //todo:記錄操做(delete)、指針、文件、行號        
44     delete[] p;
45 }
46 
47 void DetectMemoryLeaks()
48 {
49     //todo:統計並打印未釋放的內存信息
50 }
51 
52 #endif

    使用DebugMemory.h頭文件:blog

 1 //文件名:main.cpp
 2 
 3 #include "DebugMemory.h"
 4 
 5 class A
 6 {
 7 public:
 8     A(){}
 9     A(int a, int b):m_a(a), m_b(b){}
10 private:
11     int m_a;
12     int m_b;
13 }
14 
15 int main()
16 {
17     A* pA = NEW(A, 1, 2);         //new A(1, 2)
18     DEL(pA);                      //delete pA;
19 
20     A* pArray = NEW_ARRAY(A, 10); //new A[10]
21     DEL_ARRAY(pArray);            //delete[] pArray
22 
23 #ifdef DEBUG_MEMORY
24     DetectMemoryLeaks();          //內存泄漏檢測
25 #endif
26 
27     return 0;
28 }

    4、方案評價

1.C語言應用程序的內存泄漏解決方案:完美。

2.C++語言應用程序的內存泄漏解決方案

           優勢:沒有改變默認的operator new和operator delete行爲,畢竟危險。

           優勢:實用性通用性強,徹底在應用程序員的控制範圍內。由於在應用層,無論什麼版本均可以檢測內存泄漏,不用考慮跨dll調用產生的問題。

           不足:寫法習慣改變,原來是new A(1,2),要寫成NEW(A, 1, 2),若是C++能實現自定義操做符,那麼方案就完美了。

相關文章
相關標籤/搜索