轉載:原地址 http://www.leewei.org/?p=2389
前一篇文章《幾種內存泄露檢測工具的比較》比較了幾款比較流行的內存泄露檢測工具,並做出了總結。但是遺憾的是,沒有一款是既跨平臺又支持C/C++的。主要原因有兩點:一是在兩種平臺下提供的註冊鉤子函數及功能不一致,二是輸出方式比較難確定(GUI應用程序無法看到控制檯的內容)。
接下來要實現一套自己的內存檢測方式,需求如下:
1、跨平臺。支持Windows和Linux
2、多語言。支持C和C++
3、針對性。選擇性調試對象,不像以上的檢測方法,註冊鉤子函數後所有的內存分配釋放操作都被跟蹤了,資源開銷很大。然而在許多情況下只是懷疑某中或幾種對象有泄露問題,只需要調試它們即可。
4、優化性。以上的大多數檢測方式僅能在程序結束時給出一個結論,而不能得出內存操作優化措施。我們通過將感興趣的內存操作記錄下來,事後就能一目瞭然的得出優化措施。
考慮到以上要求,計劃的實現方法如下:
1、對於C語言:定義xmalloc, xcalloc, xrealloc, xstrdup, xfree五個宏,它們不僅會調用普通的函數庫版本來完成內存操作,還會將此次操作記錄日誌。
2、對於C++語言:使用宏xConstructer和xDestructer來註冊某個類的構造和析構函數,在該類的實例創建和銷燬時,這兩個宏會把該事件記錄日誌;使用重載的new操作符來完成內存分配請求,並使用宏xnew來調用此重載函數;使用宏xdelete/xdelete_array來釋放內存。xnew/xdelete/xdelete_array會調用new/new[]/delete/delete[]來完成操作,並將事件寫入日誌。
3、《高效可靠的日誌記錄模塊的構建》一文中已經實現了一個日誌記錄系統並專門提供了log_sys_*接口,在此可以方便地調用。
在談具體實現之前,先說說怎麼應用。在程序中使用此機制的方法很簡單,只要在感興趣的內存分配的地方調用相應的x版本的函數,並在釋放此對象的時候也調用x版本函數即可。比如在C程序中有如下代碼片段:
1 2 3 4 5 |
|
則生成的日誌中包含如下內容:
4/Jul/2012 17:03:38 [Debug] {test-c.c do_test_c 21} Malloc at 000F5A88 size 1.
14/Jul/2012 17:03:38 [Debug] {test-c.c do_test_c 22} Malloc at 000F6C58 size 10.
14/Jul/2012 17:03:38 [Debug] {test-c.c do_test_c 23} Malloc at 000F6D90 size 100.
14/Jul/2012 17:03:38 [Debug] {test-c.c do_test_c 24} Free at 000F6D90.
一看就會發現do_test_c函數的21、22行發生內存泄露了。當然這只是最簡單的一個例子,但也是最本質的東西。一個實際的項目可能會產生幾十萬甚至上百萬條內存操作記錄,例如下面的片段:
僅僅這幾十行日誌記錄,再一條條地比對就已經很困難了,何況是以萬爲單位的呢?因此,爲了發掘這些日誌中的財富,特意又寫了一個小程序來檢測它,運行結果如下:
在此便可一目瞭然地看出問題所在。而且還會統計出一些其他你可能感興趣的東西。
C實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
/* 頭文件 */ #define DBG_MEM_LOG #define DBG_MEM #ifdef DBG_MEM void free_d(void *, const char*, const char*, int); void *malloc_d(size_t, const char*, const char*, int); void *realloc_d(void *, size_t, const char*, const char*, int); void *calloc_d(size_t, size_t, const char*, const char*, int); char *strdup_d(const char* s, const char*, const char*, int); #define xabort() abort_d(path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #define xmalloc(s) malloc_d(s, path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #define xrealloc(p,s) realloc_d(p, s, path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #define xcalloc(c,s) calloc_d(c, s, path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #define xfree(p) free_d(p, path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #define xstrdup(s) strdup_d(s, path_find_file_name(__FILE__), __FUNCTION__, __LINE__) #else #define xabort abort #define xmalloc malloc #define xrealloc realloc #define xcalloc calloc #define xfree free #define xstrdup strdup #endif /* 源文件 */ #ifdef DBG_MEM /* 釋放內存 */ void free_d(void *ptr, const char* file, const char* func, int line) { #ifdef DBG_MEM_LOG log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Free at %p.\n", file, func, line, ptr); #endif free(ptr); } /* 分配內存 */ void *malloc_d(size_t size, const char* file, const char* func, int line) { register void *memptr = NULL; if (size > 0 && ((memptr = malloc(size)) == NULL)) { log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} Malloc exhausted.\n", file, func, line); log_close_all(); abort_d(__FILE__, __FUNCTION__, __LINE__); } else { #ifdef DBG_MEM_LOG log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Malloc at %p size %d.\n", file, func, line, memptr, size); #endif } return (memptr); } void *realloc_d(void *ptr, size_t size, const char* file, const char* func, int line) { register void *memptr = NULL; if (size > 0 && ((memptr = realloc(ptr, size)) == NULL)) { log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} Realloc exhausted.\n", file, func, line); log_close_all(); abort_d(__FILE__, __FUNCTION__, __LINE__); } else { #ifdef DBG_MEM_LOG log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Realloc from %p to %p size %d.\n", file, func, line, ptr, memptr, size); #endif } return (memptr); } void *calloc_d(size_t count, size_t size, const char* file, const char* func, int line) { register void *memptr = NULL; if (size > 0 && ((memptr = calloc(count, size)) == NULL)) { log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} Calloc exhausted.\n", file, func, line); log_close_all(); abort_d(__FILE__, __FUNCTION__, __LINE__); } else { #ifdef DBG_MEM_LOG log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Calloc at %p count %d size %d.\n", file, func, line, memptr, count, size); #endif } return (memptr); } /* 分配字符串 */ char *strdup_d(const char* s, const char* file, const char* func, int line) { char *p; if (!(p = strdup(s))) { log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} Strdup failed.\n", file, func, line); log_close_all(); abort_d(__FILE__, __FUNCTION__, __LINE__); } else { #ifdef DBG_MEM_LOG log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Strdup at %p size %d.\n", file, func, line, p, strlen(p)); #endif return p; } #endif /* DBG_MEM */ |
C++實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
|