这个内存泄漏检测工具很简单,只能检测同一个模块,同一个线程中发送的内存泄漏,对于在编写代码过程中的代码调试有一定的帮助。如果要在集成测试或功能测试中检测内存泄漏,还需借助专门的工具。
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;
}