一個簡單的記憶體洩漏檢測C工具
阿新 • • 發佈:2019-01-29
這個記憶體洩漏檢測工具很簡單,只能檢測同一個模組,同一個執行緒中傳送的記憶體洩漏,對於在編寫程式碼過程中的程式碼除錯有一定的幫助。如果要在整合測試或功能測試中檢測記憶體洩漏,還需藉助專門的工具。
1. 先取向malloc,free和calloc這幾個識別符號的定義:注意這一步非常重要,否則後面的malloc、free和calloc函式會和我們稍後在標頭檔案中定義的巨集衝突
// 取消malloc, calloc, free的巨集定義
#undef malloc
#undef calloc
#undef free
2. 定義儲存記憶體資訊的單向連結串列
/** * 定義連結串列節點,表示一個記憶體洩漏資訊 */ typedef struct _mem_node { void *ptr; // 洩漏記憶體地址 size_t block; // 洩漏記憶體大小 size_t line; // 洩露發生的程式碼行 char *filename; // 洩漏發生的檔名 struct _mem_node *next; // 下一個節點指標 } mem_node; // 定義指向頭節點的指標 mem_node *head = NULL;
3. 用於將節點加入單項鍊表的函式
/** * 產生一個節點並加入連結串列 * @param ptr 分配的記憶體地址 * @param block 分配的記憶體單元大小 * @param line 程式碼行號 * @param filename 檔名稱 */ static void mem_node_add(void *ptr, size_t block, size_t line, char *filename) { // 產生節點 mem_node *node = malloc(sizeof(mem_node)); node->ptr = ptr; node->block = block; node->line = line; node->filename = filename; node->next = NULL; // 加入連結串列頭節點 if (head) { node->next = head; head = node; } else head = node; }
4. 從單項鍊表中刪除節點的函式
/** * 從連結串列中刪除一個節點 * @param ptr 分配的記憶體地址 */ static void mem_node_remove(void *ptr) { // 判斷頭節點是否存在 if (head) { // 處理頭節點 if (head->ptr == ptr) { // 獲取頭節點的下一個節點 mem_node *pn = head->next; // 刪除頭節點 free(head); // 令頭節點指標指向下一個節點 head = pn; } else // 判斷連結串列是否為空 { // 指向節點的指標 mem_node *pn = head->next; // 指向前一個節點的指標 mem_node *pc = head; // 遍歷所有節點 while (pn) { // 獲取指向下一個節點的指標 mem_node *pnext = pn->next; if (pn->ptr == ptr) { pc->next = pnext; // 刪除當前節點 free(pn); } else pc = pc->next; pn = pnext; } } } }
5. 顯示記憶體洩露資訊報告
/**
* 顯示記憶體洩漏資訊
*/
void show_block()
{
if (head)
{
// 儲存總記憶體洩漏數量
size_t total = 0;
// 指向頭節點的指標
mem_node *pn = head;
// 輸出標題
puts("\n\n-------------------------------記憶體洩漏報告------------------------------------\n");
// 遍歷連結串列
while (pn)
{
mem_node *pnext = pn->next;
// 處理檔名
char *pfile = pn->filename, *plast = pn->filename;
while (*pfile)
{
// 找到\字元
if (*pfile == '\\')
plast = pfile + 1; // 獲取\字元的位置
pfile++;
}
// 輸出記憶體洩漏資訊
printf("位置:%s(%d), 地址:%p(%dbyte)\n", plast, pn->line, pn->ptr, pn->block);
// 累加記憶體洩漏總量
total += pn->block;
// 刪除連結串列節點
free(pn);
// 指向下一個節點
pn = pnext;
}
printf("總計記憶體洩漏:%dbyte\n", total);
}
}
6. 定義除錯用malloc函式
/**
* 用於除錯的malloc函式
* @param elem_size 分配記憶體大小
* @param filename 檔名稱
* @param line 程式碼行號
*/
void *dbg_malloc(size_t elem_size, char *filename, size_t line)
{
void *ptr = malloc(elem_size);
// 將分配記憶體的地址加入連結串列
mem_node_add(ptr, elem_size, line, filename);
return ptr;
}
7. 定義除錯用的calloc函式
/**
* 用於除錯的calloc函式
* @param count 分配記憶體單元數量
* @param elem_size 每單元記憶體大小
* @param filename 檔名稱
* @param line 程式碼行號
*/
void *dbg_calloc(size_t count, size_t elem_size, char *filename, size_t line)
{
void *ptr = calloc(count, elem_size);
// 將分配記憶體的地址加入連結串列
mem_node_add(ptr, elem_size * count, line, filename);
return ptr;
}
8. 定義除錯用的free函式
/**
* 用於除錯的free函式
* @param ptr 要釋放的記憶體地址
*/
void dbg_free(void *ptr)
{
free(ptr);
// 從連結串列中刪除節點
mem_node_remove(ptr);
}
上述程式碼應包含在一個C檔案中(例如memcheck.c),完成上述步驟,就可以利用這一組函式來檢測記憶體洩露了,需要定義如下標頭檔案,該標頭檔案應該被書寫上述函式的C檔案include:
#ifndef _MEM_CHECK_H
#define _MEM_CHECK_H
#include <stdlib.h>
// instead of malloc
#define malloc(s) dbg_malloc(s, __FILE__, __LINE__)
// instead of calloc
#define calloc(c, s) dbg_calloc(c, s, __FILE__, __LINE__)
// instead of free
#define free(p) dbg_free(p)
/**
* allocation memory
*/
void *dbg_malloc(size_t elem_size, char *filename, size_t line);
/**
* allocation and zero memory
*/
void *dbg_calloc(size_t count, size_t elem_size, char *filename, size_t line);
/**
* deallocate memory
*/
void dbg_free(void *ptr);
/**
* show memory leake report
*/
void show_block();
#endif // _MEM_CHECK_H
使用的時候只需要包含上述標頭檔案(例如命名為memcheck.h),並將上述C檔案引入到專案中即可。測試程式碼如下:
#ifdef DEBUG
#include "memcheck.h"
#endif
int main()
{
int* p;
#ifdef DEBUG
atexit(show_block); // 在程式結束後顯示記憶體洩漏報告
#endif // DEBUG
// 分配記憶體並不回收,顯示記憶體洩漏報告
p = (int*)malloc(1000);
return 0;
}