基於日誌的內存泄露檢測方法(跨平臺,支持C/C++)

轉載:原地址 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程序中有如下代碼片段:

?View Code CPP
1
2
3
4
5
char *p
p = xmalloc(1);
p = xmalloc(10);
p = xmalloc(100);
xfree(p);

則生成的日誌中包含如下內容:
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行發生內存泄露了。當然這只是最簡單的一個例子,但也是最本質的東西。一個實際的項目可能會產生幾十萬甚至上百萬條內存操作記錄,例如下面的片段:

?View Code PLAIN

僅僅這幾十行日誌記錄,再一條條地比對就已經很困難了,何況是以萬爲單位的呢?因此,爲了發掘這些日誌中的財富,特意又寫了一個小程序來檢測它,運行結果如下:




在此便可一目瞭然地看出問題所在。而且還會統計出一些其他你可能感興趣的東西。

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++實現

?View Code CPP
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
//頭文件
#ifdef DBG_MEM
 
//調試內存構造/析構函數
#ifdef DBG_MEM_LOG
#define xConstructer(c) log_printf(LOG_SYSTEM, LOG_LEVEL_DEBUG, "Constructer at %p for %s size %d.", this, #c, sizeof(*this))
#define xDestructer(c) log_printf(LOG_SYSTEM, LOG_LEVEL_DEBUG,  "Destructer at %p for %s.", this, #c)
#else
#define xConstructer(c) 
#define xDestructer(c) 
#endif
 
//重載new操作符處理
void* operator new(size_t size, const char *file, const char *func, int line);
void* operator new[](size_t size, const char *file, const char *func, int line);
void  operator delete(void* pointer, const char* file, const char *func, int line);
void  operator delete[](void* pointer, const char* file, const char *func, int line);
#define xnew new(__FILE__,__FUNCTION__,__LINE__)
 
#ifdef DBG_MEM_LOG
#define DBG_DELELTE_LOGPRINTF(p) log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Delete at %p.\n",path_find_file_name(__FILE__),__FUNCTION__,__LINE__, p)
#define DBG_DELELTE_ARRAY_LOGPRINTF(p) log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} Delete at %p.\n",path_find_file_name(__FILE__),__FUNCTION__,__LINE__, p)
#else
#define DBG_DELELTE_LOGPRINTF(p) 
#define DBG_DELELTE_ARRAY_LOGPRINTF(p) 
#endif //DBG_MEM_LOG
 
#define xdelete(p) DBG_DELELTE_LOGPRINTF(p), delete p
#define xdelete_array(p) DBG_DELELTE_ARRAY_LOGPRINTF(p), delete[] p
 
#else  //DGB_MEM
#define xConstructer(c) 
#define xDestructer(c) 
#define xnew new
#define xdelete(p) delete p
#define xdelete_array(p) delete[] p
#endif //DGB_MEM
 
 
//源文件
#ifdef DBG_MEM
void * operator new(size_t size, const char *file, const char *func, int line)
{
	void *ptr = ::operator new(size);
 
	if (!ptr){
		log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} New exhausted.\n", path_find_file_name(file), func, line);
		log_close_all();
		abort_d(file, func, line);
	}
	else
	{
#ifdef DBG_MEM_LOG
		log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} New at %p size %d.\n", path_find_file_name(file), func, line, ptr, size);
#endif
	}
 
	return(ptr);
};
 
void* operator new[](size_t size, const char *file, const char *func, int line)
{
	void *ptr = ::operator new(size);
 
	if (!ptr){
		log_printf0(LOG_SYSTEM, LOG_LEVEL_EMERG, "{%s %s %d} New[] exhausted.\n", path_find_file_name(file), func, line);
		log_close_all();
		abort_d(file, func, line);
	}
	else
	{
#ifdef DBG_MEM_LOG
		log_printf0(LOG_SYSTEM, LOG_LEVEL_DEBUG, "{%s %s %d} New[] at %p size %d.\n", path_find_file_name(file), func, line, ptr, size);
#endif
	}
 
	return(ptr);
}
 
//Effective C++規則十 ———— 重載了new一定要重載對應的delete
//但是以下delete函數僅當對象構造函數拋出異常時纔會調用
void operator delete(void* ptr, const char* file, const char* func, int line)
{
	log_printf0(LOG_SYSTEM, LOG_LEVEL_ERROR, "{%s %s %d} Thrown on initializing object at %p.\n", path_find_file_name(file), func, line, ptr);
 
	::operator delete(ptr);
}
 
void operator delete[](void* pointer, const char* file, const char* func, int line)
{
	operator delete(pointer, file, func, line);
}
 
void operator delete(void* pointer, const std::nothrow_t&)
{
	operator delete(pointer, "Unknown","Unknown", 0);
}
 
void operator delete[](void* pointer, const std::nothrow_t&)
{
	operator delete(pointer, std::nothrow);
}
 
#endif // DBG_MEM

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