该项目是一个C语言的内存检测小程序,一个印度人写的,网址如下:http://www.codeproject.com/Articles/19361/Memory-Leak-Detection-in-C。
在C语言中,我们一般都是用malloc和calloc获取堆上的内存,用free释放内存。有时,我们可能忘了用free释放我们用malloc或者calloc获取的内存,这就导致内存泄露。而这个小程序就非常容易的检测出我们的程序到底哪里出现了内存泄露,我认为这在debug模式下还是有用的。好,现在让我们来看看这个小程序是怎么样实现的。
这个小程序主要包含两个文件一个头文件leak_detector_c.h和一个源文件leak_detector_c.c。该程序把库函数malloc、calloc和free函数进行了包装,分别包装成xmalloc、xcalloc和xfree函数。当客户调用malloc、calloc函数分配内存时,实际是调用程序自定义的函数xmalloc、xcalloc,同理,free也一样。当调用malloc分配内存时(实际是调用xmalloc,xmalloc里再调用malloc),用一个队列记录分配的内存地址、内存大小、所在文件以及行数(该队列用链表实现,待会看代码就知道)。calloc as well as malloc。每当free函数调用时,就从该队列移除这个地址信息,所以如果程序执行完毕之后,如果没有free所有的内存的话,该队列就不为空。但是我们如何在程序执行完毕之后得到内存泄露的信息?在C语言中,我们有这样一个函数atexit(),该函数的作用是注册终止函数(即main执行结束后调用的函数)。OK,现在该小程序原理搞懂了,看代码就more easy 了。
leak_detector_c.h如下,详细的说明已在注释里面了。(其实我觉得没必要有注释,因为这个小程序是在是太简单了,懂点C语言的都会看明白)
#define FILE_NAME_LENGTH 256 #define OUTPUT_FILE "/home/leak_info.txt" //存放内存泄露的信息 #define malloc(size) xmalloc (size, __FILE__, __LINE__) //重新实现malloc、calloc和free #define calloc(elements, size) xcalloc (elements, size, __FILE__, __LINE__) #define free(mem_ref) xfree(mem_ref) struct _MEM_INFO //一次分配的内存信息 { void *address; //分配的地址 unsigned int size; //地址大小 char file_name[FILE_NAME_LENGTH]; //在哪个文件申请 unsigned int line; //行数 }; typedef struct _MEM_INFO MEM_INFO; struct _MEM_LEAK { //将所有malloc和calloc申请的内存串起来 MEM_INFO mem_info; struct _MEM_LEAK * next; }; typedef struct _MEM_LEAK MEM_LEAK; void add(MEM_INFO alloc_info); void erase(unsigned pos); void clear(void); void * xmalloc(unsigned int size, const char * file, unsigned int line); void * xcalloc(unsigned int elements, unsigned int size, const char * file, unsigned int line); void xfree(void * mem_ref); void add_mem_info (void * mem_ref, unsigned int size, const char * file, unsigned int line); void remove_mem_info (void * mem_ref); void report_mem_leak(void); //在atexit注册的终止函数
leak_detector_c.c文件如下:
static MEM_LEAK * ptr_start = NULL; //用这两个指针将malloc和calloc申请的内存串起来,只能本文件中使用 static MEM_LEAK * ptr_next = NULL; // 向队列中添加申请的内存信息 void add(MEM_INFO alloc_info) { MEM_LEAK * mem_leak_info = NULL; mem_leak_info = (MEM_LEAK *) malloc (sizeof(MEM_LEAK)); mem_leak_info->mem_info.address = alloc_info.address; mem_leak_info->mem_info.size = alloc_info.size; strcpy(mem_leak_info->mem_info.file_name, alloc_info.file_name); mem_leak_info->mem_info.line = alloc_info.line; mem_leak_info->next = NULL; if (ptr_start == NULL) { ptr_start = mem_leak_info; ptr_next = ptr_start; } else { ptr_next->next = mem_leak_info; ptr_next = ptr_next->next; } } //从队列中移除内存信息 void erase(unsigned pos) { unsigned index = 0; MEM_LEAK * alloc_info, * temp; if(pos == 0) { MEM_LEAK * temp = ptr_start; ptr_start = ptr_start->next; free(temp); } else { for(index = 0, alloc_info = ptr_start; index < pos; alloc_info = alloc_info->next, ++index) { if(pos == index + 1) { temp = alloc_info->next; alloc_info->next = temp->next; free(temp); break; } } } } //从队列中移除所有的内存信息 void clear() { MEM_LEAK * temp = ptr_start; MEM_LEAK * alloc_info = ptr_start; while(alloc_info != NULL) { alloc_info = alloc_info->next; free(temp); temp = alloc_info; } } //对malloc的包装 void * xmalloc (unsigned int size, const char * file, unsigned int line) { void * ptr = malloc (size); if (ptr != NULL) { add_mem_info(ptr, size, file, line); } return ptr; } //对calloc的包装 void * xcalloc (unsigned int elements, unsigned int size, const char * file, unsigned int line) { unsigned total_size; void * ptr = calloc(elements , size); if(ptr != NULL) { total_size = elements * size; add_mem_info (ptr, total_size, file, line); } return ptr; } //对free的包装 void xfree(void * mem_ref) { remove_mem_info(mem_ref); free(mem_ref); } //获取申请分配的内存信息,并将它添加到队列中 void add_mem_info (void * mem_ref, unsigned int size, const char * file, unsigned int line) { MEM_INFO mem_alloc_info; memset( &mem_alloc_info, 0, sizeof ( mem_alloc_info ) ); mem_alloc_info.address = mem_ref; mem_alloc_info.size = size; strncpy(mem_alloc_info.file_name, file, FILE_NAME_LENGTH); mem_alloc_info.line = line; add(mem_alloc_info); } //从队列中移除已分配的内存信息 void remove_mem_info (void * mem_ref) { unsigned short index; MEM_LEAK * leak_info = ptr_start; for(index = 0; leak_info != NULL; ++index, leak_info = leak_info->next) { if ( leak_info->mem_info.address == mem_ref ) { erase ( index ); break; } } } //回调函数,向文件中写入内存泄露的情况 void report_mem_leak(void) { unsigned short index; MEM_LEAK * leak_info; FILE * fp_write = fopen (OUTPUT_FILE, "wt"); char info[1024]; if(fp_write != NULL) { sprintf(info, "%s\n", "Memory Leak Summary"); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "%s\n", "-----------------------------------"); fwrite(info, (strlen(info) + 1) , 1, fp_write); for(leak_info = ptr_start; leak_info != NULL; leak_info = leak_info->next) { sprintf(info, "address : %d\n", leak_info->mem_info.address); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "size : %d bytes\n", leak_info->mem_info.size); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "file : %s\n", leak_info->mem_info.file_name); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "line : %d\n", leak_info->mem_info.line); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "%s\n", "-----------------------------------"); fwrite(info, (strlen(info) + 1) , 1, fp_write); } } clear(); }
下面我们用一个简单例子测一下:
#include <malloc.h> #include "leak_detector_c.h" int main() { char * ptr1 = (char *)malloc(10); int * ptr2 = (int *)calloc(10, sizeof(int)); float * ptr3 = (float *) calloc(15, sizeof(float)); free(ptr2); atexit(report_mem_leak); return 0; }
用gcc编译,运行之后,就可以在/home目录下找到这样一个文件leak_info.txt,该文件就记录了我们内存泄露的情况。是不是很easy。。。亲。。。。
OK,从这个小程序中,我学到两点知识:
1、链表操作
2、用atexit函数注册终止回调函数