1. 程式人生 > >一個簡單的記憶體洩漏檢測C工具

一個簡單的記憶體洩漏檢測C工具

  這個記憶體洩漏檢測工具很簡單,只能檢測同一個模組,同一個執行緒中傳送的記憶體洩漏,對於在編寫程式碼過程中的程式碼除錯有一定的幫助。如果要在整合測試或功能測試中檢測記憶體洩漏,還需藉助專門的工具。

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;
}