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也是好事啊!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • C語言中內存分配 在任何程序設計環境及語言中,內存管理都十分重要。在目前的計算機系統或嵌入式系統中,內存資源仍然是...
    一生信仰閱讀 1,188評論 0 2
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • 1.C和C++的區別?C++的特性?面向對象編程的好處? 答:c++在c的基礎上增添類,C是一個結構化語言,它的重...
    杰倫哎呦哎呦閱讀 9,627評論 0 45
  • 完整路徑 C:\Python27\Lib\site-packages\selenium\webdriver\rem...
    苦葉子閱讀 2,701評論 0 3
  • “何為美人?” “纖手,漾眸,柔腰肢。” “可否具體?” “橘子香氣。” “可否再具體?” “汝。” “何為孤寂?...
    晴天31閱讀 926評論 0 1