C语言内存检测
最近想转到Linux下的C\C++编程,但是当前工作还是以在Window下的C++开发为主,偶尔也在Linux和Mac OS开发一些小工具,但是就是开发这些小工具让我有想转到Linux下C\C++编程的念头。但是工作还是要做的,不然没得饭吃啊,所以就只能利用下班时间及周末时间研究一下,先熟悉熟悉,由于大学木有认真的学习过C语言,一直以为C语言里有bool类型,直到工作后才知道C语言中木有这个类型,o(╯□╰)o。。。,所以就从C语言学起,我已经学过C++了,为什么还要从头来学C语言呢,我从事C++开发也就一年多一点,C++博大精深,所以我连半个C++程序员都算不上。废话少说了,为将来转到Linux下进行C\C++编程做准备,我得重头开始。
但是要怎么自学才能达到我的目的呢,光看课本可不行,不久就会忘了,我想看别人开源项目应该是一个不错的选择。所以,我计划是这样的:在codeproject找一些开源项目看,无论该项目代码量是多少,就算是几十行,我想也有我们值得学习的地方,毕竟代码上传到codeproject让People mountain people sea的开发者检阅,质量也不会差到哪里去。
好了,就不多说,让我们从一个非常简单的项目说起,其实也不能说是项目,更确切的说是一个Demo。
该项目是一个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函数注册终止回调函数
ye,ye,That's all。This is enough。不是吗。。。