http://3xin2yi.info/wwwroot/tech/doku.php/tech:system:memoryleak
内存泄露是指在程序运行过程中,动态申请了部分内存空间,却没有在使用完毕后将其释放,结果导致该内存空间无法被再次使用。内存泄露是使用C或C++编程时易犯的错误之一,严重的内存泄露常常表现为:程序运行时间的越长,占用的内存越多,最终导致系统内存枯竭。
如以下代码:
int *dup_buffer(int* buffer, int size) { int *p; p = (int *) malloc(size*sizeof(int)); if (p !=0) { memcpy(p, buffer, size); } return p; }
如果程序调用该函数拷贝内存却没有在函数外部释放返回的指针,则该程序发生了内存泄露。
使用GLIBC进行内存管理时可以利用malloc提供的钩子函数进行内存分配跟踪,从而发现内存泄露。
以下代码可以作为一个桩子嵌入应用程序来检查内存泄露:
/* Prototypes for __malloc_hook, __free_hook */ #include <malloc.h> #include <pthread.h> #include <stdio.h> #ifndef MEMCHK_LIST_SIZE #define MEMCHK_LIST_SIZE 1000 #endif typedef struct memstat { unsigned int addr; unsigned int size; unsigned int caller; struct memstat *next; } memstat_t; memstat_t mem_list[MEMCHK_LIST_SIZE+2]; /* Globals */ void *(*old_malloc_hook)(size_t); void (*old_free_hook)(void *); memstat_t *listhead_free = &mem_list[0]; memstat_t *listhead_mem = &mem_list[MEMCHK_LIST_SIZE+1]; pthread_mutex_t hook_locker = PTHREAD_MUTEX_INITIALIZER; void dump_malloc(); static memstat_t *remove_from_list(memstat_t *head, unsigned int addr) { memstat_t *p1 = head; memstat_t *p2 = p1->next; while (p2 != 0) { if (p2->addr == addr) break; p1 = p2; p2 = p2->next; } if (p2 != 0) /* found */ { p1->next = p2->next; } return p2; } static void add_to_list(memstat_t *head, memstat_t *p) { p->next = head->next; head->next = p; } static void print_list(memstat_t *head) { memstat_t *p = head; int i = 0; while (p->next != 0) { printf("No.%d\taddr=0x%x\tsize=%d\tcaller=0x%x\n", i++, p->next->addr, p->next->size, p->next->caller); p = p->next; } printf("\n"); } void dump_malloc() { if (pthread_mutex_lock(&hook_locker)) return; print_list(listhead_mem); pthread_mutex_unlock(&hook_locker); } /* Prototypes for our hooks. */ static void my_init_hook (void); static void *my_malloc_hook (size_t, const void *); static void my_free_hook (void*, const void *); /* Override initializing hook from the C library. */ void (*__malloc_initialize_hook) (void) = my_init_hook; static void my_init_hook (void) { int i; old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; /* first element for free*/ listhead_free = &mem_list[0]; /* link free list */ for (i = 0; i < MEMCHK_LIST_SIZE; i++) { mem_list[i].addr = 0; mem_list[i].size = 0; mem_list[i].caller = 0; mem_list[i].next = &mem_list[i+1]; } mem_list[MEMCHK_LIST_SIZE].addr = 0; mem_list[MEMCHK_LIST_SIZE].size = 0; mem_list[MEMCHK_LIST_SIZE].caller = 0; mem_list[MEMCHK_LIST_SIZE].next = 0; /* last element for busy */ listhead_mem = &mem_list[MEMCHK_LIST_SIZE+1]; /* clean busy list */ listhead_mem->addr = 0; listhead_mem->size = 0; listhead_mem->caller = 0; listhead_mem->next = 0; } static void *my_malloc_hook (size_t size, const void *caller) { void *result; memstat_t *p; if (pthread_mutex_lock(&hook_locker)) return 0; /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ result = malloc (size); /* Save underlying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; p = remove_from_list(listhead_free, 0); if (p == 0) { pthread_mutex_unlock(&hook_locker); return 0; } p->addr = (unsigned int)result; p->size = size; p->caller = (unsigned int)caller; p->next = 0; add_to_list(listhead_mem, p); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; pthread_mutex_unlock(&hook_locker); return result; } static void my_free_hook (void *ptr, const void *caller) { memstat_t *p; if (pthread_mutex_lock(&hook_locker)) return; /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ free (ptr); /* Save underlying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; p = remove_from_list(listhead_mem, (unsigned int)ptr); if (p == 0) { pthread_mutex_unlock(&hook_locker); return; } p->addr = 0; p->size = 0; p->caller = 0; p->next = 0; add_to_list(listhead_free, p); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; pthread_mutex_unlock(&hook_locker); }
将以上代码保存为memchk.c,直接通过include将之引入到被测试的程序中:
#include "memchk.c" #include <memory.h> int *dup_buffer(int* buffer, int size) { int *p; p = (int *) malloc(size*sizeof(int)); if (p !=0) { memcpy(p, buffer, size); } return p; } int main(int argc, char** argv) { int src[100]; int i; int *pDes; for (i = 0; i < 100; i++) src[i] = i; pDes = dup_buffer(src, 100); dump_malloc(); return 0; }
运行结果为:
No.0 addr=0x8dd6008 size=400 caller=0x8d78ee
有些遗憾的是,0x8d78ee并非函数dup_buffer的地址,而是 malloc_hook_ini的地址。但是,如果我们再增加一次对dup_buffer的调用,如下:
#include "memchk.c" #include <memory.h> int *dup_buffer(int* buffer, int size) { int *p; p = (int *) malloc(size*sizeof(int)); if (p !=0) { memcpy(p, buffer, size); } return p; } int main(int argc, char** argv) { int src[100]; int i; int *pDes; for (i = 0; i < 100; i++) src[i] = i; pDes = dup_buffer(src, 100); pDes = dup_buffer(src, 100); dump_malloc(); return 0; }
则运行结果为:
No.0 addr=0x83431a0 size=400 caller=0x8048916 No.1 addr=0x8343008 size=400 caller=0x8d78ee
通过使用“add2line -e test 0x8048916”可得到“test2.c:8”的结果,正是malloc被调用的位置。
对于单一模块的初期开发阶段,这种方法尚且有效。但若是出于集成阶段,模块数量众多且相互耦合,代码庞大异常,即是捕捉到malloc产生的泄漏也不易定位——可能是一个公共函数引起,而这个公共函数为多个模块所调用。因此,建议在软件模块的初期开发阶段,为本模块定义专门的内存管理函数,模块内所有的内存分配和释放都是用专有函数,禁止malloc和free,这样不仅不需要为malloc挂接钩子函数,也能更准确地给出泄漏处的详细信息。比如:
void *my_malloc(unsigned int size, int line, char* pFunc); void *my_free(void *ptr, int line, char* pFunc);
mtrace是GNU C库中自带的一个内存泄露检测工具,使用方法为:
依旧是上面的例子代码:
#include <mcheck.h> #include <memory.h> int *dup_buffer(int* buffer, int size) { int *p; p = (int *) malloc(size*sizeof(int)); if (p !=0) { memcpy(p, buffer, size); } return p; } int main(int argc, char** argv) { int src[100]; int i; int *pDes; mtrace(); for (i = 0; i < 100; i++) src[i] = i; pDes = dup_buffer(src, 100); pDes = dup_buffer(src, 100); muntrace(); return 0; }
运行:
export MALLOC_TRACE=.log gcc -g test.c -o test ./test mtrace test .log
结果为:
Memory not freed: ----------------- Address Size Caller 0x08ba3378 0x190 at /home/liclin/test2.c:8 0x08ba3510 0x190 at /home/liclin/test2.c:8
mtrace本身就是通过malloc的钩子函数实现的,因此面临相同的问题——在大规模复杂系统面前无计可施。
以上内存泄露检测的方法仅适用于GNU Linux环境下用户空间的程序,如果是内核代码,事情会变得复杂一些,但思路是相同的。