evn:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)
摘要:
- 方法簡介
- 如何檢測項目中是否有內(nèi)存泄漏(附代碼)
- 如何定位項目中的內(nèi)存泄漏(附代碼)
- 心得和建議
1.方法簡介
? 這種方法原理很簡單, 正常情況下程序啟動到正常終止malloc和free調(diào)用的次數(shù)應該相同, 如果malloc調(diào)用次數(shù)>free調(diào)用次數(shù), 那么
項目中就會出現(xiàn)內(nèi)存泄漏∏蛱危基于上述原理, 我們可以自己封裝一套malloc和free,然后在里面做點手腳即可, 當然過程中還是有一些地方需要注意,詳情請看下文~
2.如何檢測項目中是否有內(nèi)存泄漏
? 如果僅僅是確定項目中是否有內(nèi)存泄漏的話,可以定義1個計數(shù)器count, 放在我們重新封裝的test_malloc, test_free函數(shù)中。當調(diào)用test_malloc的時候, count++, 當調(diào)用free的時候count--案怯。當程序結(jié)束運行的時候, 如果count大于0, 則說明有內(nèi)存泄漏; 如果count等于0說明沒有內(nèi)存泄漏; 如果count 小于零, 那就見鬼了。哈哈澎办, 代碼如下, 附詳細注釋:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
static pthread_mutex_t lock;/*鎖count*/
static int count = 0;/*計數(shù)器, malloc: count++, free: count--*/
void * test_malloc(size_t size)
{
void * new_mem;
assert(size != 0);
new_mem = malloc(size);
if(new_mem)
{/*注意當malloc成功時, 才能操作count*/
pthread_mutex_lock(&lock);
++count;
pthread_mutex_unlock(&lock);
}
return new_mem;
}
void test_free(void * ptr)
{
assert(ptr != NULL);/*當free(NULL)時, 說明程序中有不合理的地方, 直接退出*/
free(ptr);
pthread_mutex_lock(&lock);
--count;
pthread_mutex_unlock(&lock);
}
void mem_leak_check_result(void)
{
int temp;
pthread_mutex_lock(&lock);
temp = count;
pthread_mutex_unlock(&lock);
if(temp > 0)
{
printf("memery leak!\n");
}
else if(temp == 0)
{
printf("no memery leak!\n");
}
else
{
printf("pigs might fly!\n");
}
}
int mem_leak_check_init(void)
{
if(pthread_mutex_init(&lock, NULL) != 0)
{
return -1;
}
return 0;
}
void mem_leak_check_destroy(void)
{
pthread_mutex_destroy(&lock);
}
上述代碼比較簡單,就不附上測試用例了, 總結(jié)幾點如下:
- 自己封裝的test_malloc和test_free必須是線程安全的, 因此要對變量count進行加鎖嘲碱。
- 在test_malloc中, 調(diào)用c庫malloc成功時再去操作count, 如下的寫法是錯誤的:
/*錯誤代碼示范*/
void * test_malloc(size_t size)
{
assert(size != 0);
pthread_mutex_lock(&lock);
++count;
pthread_mutex_unlock(&lock);
return malloc(size);
}
- 注意在test_malloc中要對傳入?yún)?shù)size的校驗:
assert(size != 0); 在堆上開辟0字節(jié)的內(nèi)存顯然是錯誤的,操作malloc(0)返回的非NULL指針不合法局蚀,會造成踩內(nèi)存麦锯。 - 注意在test_free中要檢測參數(shù)為NULL的情況, 雖然free(NULL)合法, 但是明顯程序中不應該出現(xiàn)這樣的情景。
- 以上代碼同樣適用于多進程fork模型, 想一想為什么琅绅?
3.如何定位項目中的內(nèi)存泄漏
?通常來說离咐,我們只要找到發(fā)生內(nèi)存泄漏時調(diào)用malloc的具體位置, 那么我們就找到了問題所在。接下只需要檢查此處malloc該free的地方是否有遺漏即可奉件。
?2中的方法很明顯不是我們想要的結(jié)果, 但卻是一個很好的思路, 我們很容易想到這樣一種方法:
?首先建立一個映射表map, 將調(diào)用malloc時所在的文件和行數(shù)作為value, malloc調(diào)用成功時的返回值作為key, 然后將key:value存入map中; 當調(diào)用free時(free中傳入的參數(shù)ptr即為key) 然后刪除map中對應的key宵蛀。程序正常結(jié)束時,我們可以根據(jù)map中存儲的內(nèi)容來檢查內(nèi)存泄漏情況:如無內(nèi)存泄漏, map元素個數(shù)是0;如果map中元素個數(shù)大于0, 則說明存在內(nèi)存泄漏, 遍歷map, 即可將內(nèi)存泄漏對應的malloc位置信息輸出县貌。
下面給出完整實現(xiàn)代碼和測試用例:
/*mem_leak_test.h*/
#ifndef MEM_LEAK_TEST_H
#define MEM_LEAK_TEST_H
#define test_free(p) test_safe_free(__FILE__, __LINE__, p)
#define test_malloc(s) test_safe_malloc(__FILE__, __LINE__, s)
extern void test_safe_free(const char * file, size_t line, void * ptr);
extern void * test_safe_malloc(const char * file, size_t line, size_t size);
extern void mem_leak_test_result(void);
extern int mem_leak_test_init(void);
extern void mem_leak_test_destroy(void);
#endif
/*mem_leak_test.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <map>
#include <pthread.h>
#include <assert.h>
#include "mem_leak_test.h"
static pthread_mutex_t lock;
static std::map<unsigned long, std::string> cache;
static void err_exit(const char * info)
{
fprintf(stderr, "%s\n", info);
exit(1);
}
void * test_safe_malloc(const char * file, size_t line, size_t size)
{
void * mem = malloc(size);
assert(size != 0);
if(mem)
{
char buf[266] = {0};
snprintf(buf, sizeof(buf), "file[%s], line[%d]", file, line);
pthread_mutex_lock(&lock);
cache.insert(std::make_pair((unsigned long)mem, buf));
pthread_mutex_unlock(&lock);
}
return mem;
}
void test_safe_free(const char * file, size_t line, void * ptr)
{
size_t cnt;
std::map<unsigned long, std::string>::iterator it;
assert(ptr != NULL);
free(ptr);
pthread_mutex_lock(&lock);
cnt = cache.erase((unsigned long)ptr);
if(cnt == 0)
{
err_exit("cache.erase nothing");
}
pthread_mutex_unlock(&lock);
}
void mem_leak_test_result(void)
{
std::map<unsigned long, std::string>::iterator it;
pthread_mutex_lock(&lock);
if(cache.size() == 0)
{
printf("Congratulations, there is no memery leak!\n");
return;
}
printf("memery leak info: \n");
for(it = cache.begin(); it != cache.end(); it++)
{
printf("\tmem addr: %ld, location info: %s\n", it->first, it->second.c_str());
}
pthread_mutex_unlock(&lock);
}
int mem_leak_test_init(void)
{
if(pthread_mutex_init(&lock, NULL) != 0)
{
return -1;
}
return 0;
}
void mem_leak_test_destroy(void)
{
pthread_mutex_destroy(&lock);
}
/*test_main.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mem_leak_test.h"
#define my_malloc(s) test_malloc(s)
#define my_free(p) test_free(p)
#define ARR_SIZE 10
void err_exit(const char * info)
{
fprintf(stderr, "%s\n", info);
exit(1);
}
void test(void)
{
void * arr[ARR_SIZE];
int i;
for(i = 0; i < ARR_SIZE; ++i)
{
arr[i] = NULL;
}
for(i = 0; i < ARR_SIZE; ++i)
{
arr[i] = my_malloc(sizeof(int));
if(!arr[i])
{
err_exit("my_malloc failed!");
}
}
/*there is lack of 2 free() deliberately*/
for(i = 0; i < ARR_SIZE - 2; ++i)
{
my_free(arr[i]);
}
}
int main(int argc, const char * const argv[])
{
/*init memery leak check module*/
if(mem_leak_test_init() != 0)
{
return -1;
}
/*simulate a project which may have a memory leak*/
test();
/*show memery leak check result*/
mem_leak_test_result();
/*destroy memery leak check module*/
mem_leak_test_destroy();
return 0;
}
幾點說明:
- 為了方便演示這里就直接借用c++ STL中的map作為cache术陶。
- cache同樣需要一把鎖來保證數(shù)據(jù)安全。
- 為方便傳入malloc,free調(diào)用處的文件名和行號等信息, 已將test_malloc和test_free定義為宏煤痕。(本代碼中free對應的文件名和行號等信息暫未用到)
- test_main.c中的test()函數(shù)模擬存在內(nèi)存泄漏的項目, 看以看到在其調(diào)用過程中故意漏掉了2個free梧宫。
程序編譯運行結(jié)果如下:
study@study-virtual-machine:~/study/c/mem_leak_test$ ls
mem_leak_test.c mem_leak_test.h test_main.c
study@study-virtual-machine:~/study/c/mem_leak_test$ g++ test_main.c mem_leak_test.c -o mem_leak_test
study@study-virtual-machine:~/study/c/mem_leak_test$ ./mem_leak_test
memery leak info:
mem addr: 161508656, location info: file[test_main.c], line[31]
mem addr: 161508752, location info: file[test_main.c], line[31]
study@study-virtual-machine:~/study/c/mem_leak_test$
4.心得和建議
- 良好的習慣是: 項目中調(diào)用malloc或包含有資源申請的模塊時應該在旁邊注釋到哪里應該釋放。
- 測試內(nèi)存泄漏最容易想到的是程序正常邏輯下的測試, 其實往往內(nèi)存泄漏的地方都發(fā)生在程序異常處理中, 而產(chǎn)生這類異常的原因通常是與外界輸入相關的摆碉。因此我們在測試項目內(nèi)存泄漏時要著重測試上述異常條件對應的錯誤處理分支塘匣。
- malloc的返回值一定要檢查, 尤其在開辟大塊內(nèi)存時或者在內(nèi)存資源緊缺的嵌入式平臺上。雖然malloc失敗可能會導致邏輯進行不下去, 但是打印個log也是好事跋锏邸忌卤!