c語言內(nèi)存泄漏檢測方法之封裝malloc,free詳解

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

摘要:

  1. 方法簡介
  2. 如何檢測項目中是否有內(nèi)存泄漏(附代碼)
  3. 如何定位項目中的內(nèi)存泄漏(附代碼)
  4. 心得和建議

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.心得和建議

  1. 良好的習慣是: 項目中調(diào)用malloc或包含有資源申請的模塊時應該在旁邊注釋到哪里應該釋放。
  2. 測試內(nèi)存泄漏最容易想到的是程序正常邏輯下的測試, 其實往往內(nèi)存泄漏的地方都發(fā)生在程序異常處理中, 而產(chǎn)生這類異常的原因通常是與外界輸入相關的摆碉。因此我們在測試項目內(nèi)存泄漏時要著重測試上述異常條件對應的錯誤處理分支塘匣。
  3. malloc的返回值一定要檢查, 尤其在開辟大塊內(nèi)存時或者在內(nèi)存資源緊缺的嵌入式平臺上。雖然malloc失敗可能會導致邏輯進行不下去, 但是打印個log也是好事跋锏邸忌卤!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市楞泼,隨后出現(xiàn)的幾起案子驰徊,更是在濱河造成了極大的恐慌笤闯,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棍厂,死亡現(xiàn)場離奇詭異颗味,居然都是意外死亡,警方通過查閱死者的電腦和手機牺弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門浦马,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人张漂,你說我怎么就攤上這事捐韩。” “怎么了鹃锈?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵荤胁,是天一觀的道長。 經(jīng)常有香客問我屎债,道長仅政,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任盆驹,我火速辦了婚禮圆丹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躯喇。我一直安慰自己辫封,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布廉丽。 她就那樣靜靜地躺著倦微,像睡著了一般。 火紅的嫁衣襯著肌膚如雪正压。 梳的紋絲不亂的頭發(fā)上欣福,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音焦履,去河邊找鬼拓劝。 笑死,一個胖子當著我的面吹牛嘉裤,可吹牛的內(nèi)容都是我干的郑临。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼屑宠,長吁一口氣:“原來是場噩夢啊……” “哼厢洞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤犀变,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秋柄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體获枝,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年骇笔,在試婚紗的時候發(fā)現(xiàn)自己被綠了省店。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡笨触,死狀恐怖懦傍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芦劣,我是刑警寧澤粗俱,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站虚吟,受9級特大地震影響寸认,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜串慰,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一偏塞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邦鲫,春花似錦灸叼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滔以,卻和暖如春沧卢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背醉者。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工但狭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撬即。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓立磁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剥槐。 傳聞我的和親對象是個殘疾皇子唱歧,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • C語言中內(nèi)存分配 在任何程序設計環(huán)境及語言中,內(nèi)存管理都十分重要。在目前的計算機系統(tǒng)或嵌入式系統(tǒng)中颅崩,內(nèi)存資源仍然是...
    一生信仰閱讀 1,151評論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理几于,服務發(fā)現(xiàn),斷路器沿后,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 1.C和C++的區(qū)別沿彭?C++的特性?面向?qū)ο缶幊痰暮锰帲?答:c++在c的基礎上增添類尖滚,C是一個結(jié)構(gòu)化語言喉刘,它的重...
    杰倫哎呦哎呦閱讀 9,492評論 0 45
  • 完整路徑 C:\Python27\Lib\site-packages\selenium\webdriver\rem...
    苦葉子閱讀 2,658評論 0 3
  • “何為美人睦裳?” “纖手,漾眸撼唾,柔腰肢廉邑。” “可否具體倒谷?” “橘子香氣鬓催。” “可否再具體恨锚?” “汝宇驾。” “何為孤寂猴伶?...
    晴天31閱讀 919評論 0 1