c语言内存泄漏检测方法之封装malloc,free详解

evn:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

摘要:

  1. 方法简介
  2. 如何检测项目中是否有内存泄漏(附代码)
  3. 如何定位项目中的内存泄漏(附代码)
  4. 心得和建议

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 <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;/*计数器, 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 <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同样需要一把锁来保证数据安全。
  • 为方便传入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.心得和建议

  1. 良好的习惯是: 项目中调用malloc或包含有资源申请的模块时应该在旁边注释到哪里应该释放。
  2. 测试内存泄漏最容易想到的是程序正常逻辑下的测试, 其实往往内存泄漏的地方都发生在程序异常处理中, 而产生这类异常的原因通常是与外界输入相关的。因此我们在测试项目内存泄漏时要着重测试上述异常条件对应的错误处理分支。
  3. malloc的返回值一定要检查, 尤其在开辟大块内存时或者在内存资源紧缺的嵌入式平台上。虽然malloc失败可能会导致逻辑进行不下去, 但是打印个log也是好事啊!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是...
    一生信仰阅读 1,157评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 1.C和C++的区别?C++的特性?面向对象编程的好处? 答:c++在c的基础上增添类,C是一个结构化语言,它的重...
    杰伦哎呦哎呦阅读 9,514评论 0 45
  • 完整路径 C:\Python27\Lib\site-packages\selenium\webdriver\rem...
    苦叶子阅读 2,673评论 0 3
  • “何为美人?” “纤手,漾眸,柔腰肢。” “可否具体?” “橘子香气。” “可否再具体?” “汝。” “何为孤寂?...
    晴天31阅读 920评论 0 1