c/c++没有垃圾回收机制,因此可能会出现内存泄漏
出现原因:内存分配与内存释放,没有做到匹配
如何知道内存泄漏:
每次malloc/calloc/realloc 就 +1
每次free 就 -1
如果正常退出,不为0,说明存在内存泄漏
如何定位哪一行引起内存泄漏
1.c自带的宏,__FILE__
、_ FUNCTION _
和、__LINE__
,可以定位到具体文件,函数,哪一行
2.builtin_return_address()
,返回函数在哪个地方调用的代码段地址(后续通过addr2line命令解析出现的位置)
如何去实现
通过hook的方法,可以在不修改原来代码的情况下,进行检测。
方法一:dlsym实现hook
如何记录呢,每次malloc都创建一个文件,每次free就删除文件,运行完后,看有多少文件就行了。
extern void *__libc_malloc(size_t size); int enable_malloc_hook = 1; extern void __libc_free(void* p); int enable_free_hook = 1; // func --> malloc() { __builtin_return_address(0)} // callback --> func --> malloc() { __builtin_return_address(1)} // main --> callback --> func --> malloc() { __builtin_return_address(2)} //calloc, realloc void *malloc(size_t size) { if (enable_malloc_hook) { enable_malloc_hook = 0; void *p = __libc_malloc(size); void *caller = __builtin_return_address(1); // 1 char buff[128] = {0};//用于存放文件名 sprintf(buff, "./mem/%p.mem", p); FILE *fp = fopen(buff, "w"); fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, p, size);//将调用malloc的位置信息 写入到 文件流中 fflush(fp);//强迫将缓冲区内的数据写回参数stream指定的文件中 //fclose(fp); //free enable_malloc_hook = 1; return p; } else { return __libc_malloc(size); } return NULL; } void free(void *p) { if (enable_free_hook) { enable_free_hook = 0; char buff[128] = {0}; sprintf(buff, "./mem/%p.mem", p); if (unlink(buff) < 0) { // no exist 删除该文件,如果小于0就代表不存在 printf("double free: %p\n", p); } __libc_free(p); // rm -rf p.mem enable_free_hook = 1; } else { __libc_free(p); } }
编译前要加上-g
因此可以通过查看目录下有没有 ./mem/xxxxxxx.mem的文件,有的话就说明内存泄漏了。
void *caller = __builtin_return_address(1);
的返回值caller就是 代码段的地址(文章最后有介绍参数)。
然后通过以下命令(addr2line)(-e表示execute -a表示代码段地址)可以查看 内存泄漏的行数
addr2line -f -e memleak -a 0x4009f7
注意点1:
自己实现一个malloc,里面执行printf,发现一直在malloc里面循环,因为printf本身内部就实现了一个malloc,这样就会陷入一个循环
size=10是我们调用的,1024是printf内部调用的
malloc内部调用__libc_malloc这个函数
因此通过enable_malloc_hook来标识,是否使用自己定义的malloc,在printf的前enable_malloc_hook置为0,printf后enable_malloc_hook=1,来避免这个问题
注意点2:
这边不能添加fclose(); 因为fclose()内部就调用了free
如果执行fclose(fp)
这样导致会free
(fclose内部调用了free)掉一个系统指针,而这个指针本身就是不存在的,因此会报double free的错误。
方法二:宏定义实现hook
通过宏定义的方式,实现hook。
实现的方式是一样的,还是通过文件保存与删除的方式来记录是否有内存泄漏,只是换成了__FILE__
, __LINE__
(系统提供的宏定义),获取当前的文件和行数。
void *malloc_hook(size_t size, const char *file, int line) { void *p = malloc(size); char buff[128] = {0}; sprintf(buff, "./mem/%p.mem", p); FILE *fp = fopen(buff, "w"); fprintf(fp, "[+%s:%d] --> addr:%p, size:%ld\n", file, line, p, size); fflush(fp); fclose(fp);//这种方法里面是可以用fclose的 return p; } void free_hook(void *p, const char *file, int line) { char buff[128] = {0}; sprintf(buff, "./mem/%p.mem", p); if (unlink(buff) < 0) { // no exist printf("double free: %p\n", p); return ; } free(p); } //宏定义,这样子就很方便,想要调试,想知道内存泄漏的时候,可以开启debug,不测试可以关掉 //不能放在前面,因为printf会调用malloc,会导致陷入死循环 #if 1 #define malloc(size) malloc_hook(size, __FILE__, __LINE__) #define free(p) free_hook(p, __FILE__, __LINE__) #endif
方式三:修改__malloc_hook和 __free_hook
其中caller就是代码段的地址,通过addr2line命令可以获取 出现内存泄漏的行号。
typedef void *(*malloc_hook_t)(size_t size, const void *caller); malloc_hook_t malloc_f; typedef void (*free_hook_t)(void *p, const void *caller); free_hook_t free_f; int replaced = 0;//为0代表指向系统自带的,如果为1就代表指向自己实现的hook void mem_trace(void); void mem_untrace(void); void *malloc_hook_f(size_t size, const void *caller) { mem_untrace();//解除hook,不然下面会继续执行自己的malloc_hook会导致死循环 void *ptr = malloc(size); //printf("+%p: addr[%p]\n", caller, ptr); char buff[128] = {0}; sprintf(buff, "./mem/%p.mem", ptr); FILE *fp = fopen(buff, "w"); fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, ptr, size);//caller就是代码段的地址 用addr2line命令查看行数 fflush(fp); fclose(fp); //free mem_trace();//开启hook return ptr; } void *free_hook_f(void *p, const void *caller) { mem_untrace(); //printf("-%p: addr[%p]\n", caller, p); char buff[128] = {0}; sprintf(buff, "./mem/%p.mem", p); if (unlink(buff) < 0) { // no exist printf("double free: %p\n", p); return ; } free(p); mem_trace(); } void mem_trace(void) { //mtrace replaced = 1; malloc_f = __malloc_hook; //将 系统调用的malloc_hook存储起来 free_f = __free_hook;//将 系统调用的free_hook存储起来 __malloc_hook = malloc_hook_f;//用自己实现的malloc_hook 取代 __free_hook = free_hook_f;//用自己实现的free_hook 取代 } void mem_untrace(void) { __malloc_hook = malloc_f;//恢复 __free_hook = free_f; replaced = 0; }
测试
mem_trace(); void *p1 = malloc(10); void *p2 = malloc(20); //calloc, realloc free(p1); void *p3 = malloc(20); void *p4 = malloc(20); free(p2); free(p4); mem_untrace();
方式四:mtrace
补充:
1. __builtin_return_address()使用
返回的是函数代码段的地址
__builtin_return_address()中的参数,0代表调用的地方,1代表调用的上一级栈,2代表调用的上两级栈
func--->maloc() {__builtin_return_address(0)} main-->func--->maloc() {__builtin_return_address(1)}