evn:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)
摘要:
方法简介
如何检测项目中是否有内存泄漏(附代码)
如何定位项目中的内存泄漏(附代码)
心得和建议
1.方法简介
这种方法原理很简单, 正常情况下程序启动到正常终止malloc和free调用的次数应该相同, 如果malloc调用次数>free调用次数, 那么
项目中就会出现内存泄漏。基于上述原理, 我们可以自己封装一套malloc和free,然后在里面做点手脚即可, 当然过程中还是有一些地方需要注意,详情请看下文~
2.如何检测项目中是否有内存泄漏
如果仅仅是确定项目中是否有内存泄漏的话,可以定义1个计数器count, 放在我们重新封装的test_malloc, test_free函数中。当调用test_malloc的时候, count++, 当调用free的时候count--。当程序结束运行的时候, 如果count大于0, 则说明有内存泄漏; 如果count等于0说明没有内存泄漏; 如果count 小于零, 那就见鬼了。哈哈, 代码如下, 附详细注释:
#include
#include
#include
#include
#include
#include
static pthread_mutex_t lock;/*锁count*/
static int count = 0;/*计数器, 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);
}
上述代码比较简单,就不附上测试用例了, 总结几点如下:
自己封装的test_malloc和test_free必须是线程安全的, 因此要对变量count进行加锁。
在test_malloc中, 调用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中要对传入参数size的校验:
assert(size != 0); 在堆上开辟0字节的内存显然是错误的,操作malloc(0)返回的非NULL指针不合法,会造成踩内存。
注意在test_free中要检测参数为NULL的情况, 虽然free(NULL)合法, 但是明显程序中不应该出现这样的情景。
以上代码同样适用于多进程fork模型, 想一想为什么?
3.如何定位项目中的内存泄漏
通常来说,我们只要找到发生内存泄漏时调用malloc的具体位置, 那么我们就找到了问题所在。接下只需要检查此处malloc该free的地方是否有遗漏即可。
2中的方法很明显不是我们想要的结果, 但却是一个很好的思路, 我们很容易想到这样一种方法:
首先建立一个映射表map, 将调用malloc时所在的文件和行数作为value, malloc调用成功时的返回值作为key, 然后将key:value存入map中; 当调用free时(free中传入的参数ptr即为key) 然后删除map中对应的key。程序正常结束时,我们可以根据map中存储的内容来检查内存泄漏情况:如无内存泄漏, map元素个数是0;如果map中元素个数大于0, 则说明存在内存泄漏, 遍历map, 即可将内存泄漏对应的malloc位置信息输出。
下面给出完整实现代码和测试用例:
/*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
#include
#include
#include
#include
#include
#include
#include
#include
#include "mem_leak_test.h"
static pthread_mutex_t lock;
static std::map 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::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::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
#include
#include
#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同样需要一把锁来保证数据安全。
为方便传入malloc,free调用处的文件名和行号等信息, 已将test_malloc和test_free定义为宏。(本代码中free对应的文件名和行号等信息暂未用到)
test_main.c中的test()函数模拟存在内存泄漏的项目, 看以看到在其调用过程中故意漏掉了2个free。
程序编译运行结果如下:
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.心得和建议
良好的习惯是: 项目中调用malloc或包含有资源申请的模块时应该在旁边注释到哪里应该释放。
测试内存泄漏最容易想到的是程序正常逻辑下的测试, 其实往往内存泄漏的地方都发生在程序异常处理中, 而产生这类异常的原因通常是与外界输入相关的。因此我们在测试项目内存泄漏时要着重测试上述异常条件对应的错误处理分支。
malloc的返回值一定要检查, 尤其在开辟大块内存时或者在内存资源紧缺的嵌入式平台上。虽然malloc失败可能会导致逻辑进行不下去, 但是打印个log也是好事啊!