1.内存泄漏(a.是否有内存泄漏 b.在哪里有内存泄漏)
2.try-catch
调用malloc 没有调用free
#include<stdio.h>#include<stdlib.h>intmain(){void*p1=malloc(5);void*p2=malloc(10);void*p3=malloc(15);free(p1);free(p3);return0;}上面这个程序造成了内存泄漏
void*_malloc(size_tsize){printf("_malloc\n");}void_free(void*ptr){printf("_free\n");}#definemalloc(size)_malloc(size)#definefree(ptr)_free(ptr)intmain(){void*p1=malloc(5);void*p2=malloc(10);void*p3=malloc(15);free(p1);free(p3);return0;}可以根据打印值判断malloc和free个数不匹配
为了得到更详细的信息,我们可以将文件名和行号打印出来:
void*_malloc(size_tsize,constchar*filename,intline){void*ptr=malloc(size);printf("[+]_malloc: %p, filename: %s, line: %d\n",ptr,filename,line);returnptr;}void_free(void*ptr,constchar*filename,intline){printf("[-]_free, filename: %s, line: %d\n",filename,line);free(ptr);}#definemalloc(size)_malloc(size,__FILE__,__LINE__)#definefree(ptr)_free(ptr,__FILE__,__LINE__)为了输出信息更直观,我们可以在malloc时创建一个文件,free时删除这个文件,最后看看是否有文件生成就可知道是否发生内存泄漏
void*_malloc(size_tsize,constchar*filename,intline){void*ptr=malloc(size);charbuff[128]={0};sprintf(buff,"./mem/%p.mem",ptr);FILE*fp=fopen(buff,"w");fprintf(fp,"[+]addr: %p, filename: %s, line: %d\n",ptr,buff,line);fflush(fp);fclose(fp);//printf("[+]_malloc: %p, filename: %s, line: %d\n", ptr, filename, line);returnptr;}void_free(void*ptr,constchar*filename,intline){charbuff[128]={0};sprintf(buff,"./mem/%p.mem",ptr);if(unlink(buff)<0){printf("double free: %p\n",ptr);return;}//printf("[-]_free, filename: %s, line: %d\n", filename, line);returnfree(ptr);}进入mem目录,可以看到0x5601fae8f4b0.mem
查看文件内容:
可以看到更详细的泄漏内容
也可以使用hook技术
typedefvoid*(*malloc_t)(size_tsize);malloc_tmalloc_f=NULL;typedefvoid(*free_t)(void*ptr);free_tfree_f=NULL;void*malloc(size_tsize){printf("malloc \n");}voidfree(void*ptr){printf("free \n");}voidinit_hook(void){if(!malloc_f){malloc_f=dlsym(RTLD_NEXT,"malloc");}if(!free_f){free_f=dlsym(RTLD_NEXT,"free");}}intmain(){init_hook();void*p1=malloc(5);void*p2=malloc(10);void*p3=malloc(15);free(p1);free(p3);return0;}但是运行该程序会发现发生了segmentation fault,这时因为printf本身也会调用malloc,造成了递归调用。
intenable_malloc_hook=1;intenable_free_hook=1;void*malloc(size_tsize){void*ptr=NULL;if(enable_malloc_hook){enable_malloc_hook=0;printf("malloc \n");ptr=malloc_f(size);enable_malloc_hook=1;}else{ptr=malloc_f(size);}returnptr;}voidfree(void*ptr){if(enable_free_hook){enable_free_hook=0;printf("free \n");enable_free_hook=1;}else{free_f(ptr);}}可以做一个修改,让printf调用只调用一次。这样就不会递归调用了。
我们仍然可以在malloc时创建一个文件,在free时删除相应的文件:
void*malloc(size_tsize){void*ptr=NULL;if(enable_malloc_hook){enable_malloc_hook=0;printf("malloc \n");ptr=malloc_f(size);charfilename[128]={0};sprintf(filename,"./mem/%p.mem",ptr);FILE*fp=fopen(filename,"w");fprintf(fp,"[+]addr: %p, filename: %s, line: %d",ptr,__FILE__,__LINE__);enable_malloc_hook=1;}else{ptr=malloc_f(size);}returnptr;}但是,这时你会发现,我们打印的行号信息始终不变,而之前是宏定义,会在使用的地方展开,但是这里是一个函数,所以行号不会跟随使用的地方发生变化。
解决这个问题可以使用__builtin_return_address,这个函数是编译器自带的函数
void*malloc(size_tsize){void*ptr=NULL;if(enable_malloc_hook){enable_malloc_hook=0;printf("malloc \n");ptr=malloc_f(size);//0参数表示上一级 1向上反二级//main -> f1() -> f2() -> f3() {}void*caller=__builtin_return_address(0);charfilename[128]={0};sprintf(filename,"./mem/%p.mem",ptr);FILE*fp=fopen(filename,"w");fprintf(fp,"[+]caller: %p, addr: %p, size: %ld",caller,ptr,size);fflush(fp);enable_malloc_hook=1;}else{ptr=malloc_f(size);}returnptr;}voidfree(void*ptr){if(enable_free_hook){enable_free_hook=0;charbuff[128]={0};sprintf(buff,"./mem/%p.mem",ptr);if(unlink(buff)<0){printf("double free: %p\n",ptr);return;}free_f(ptr);enable_free_hook=1;}else{free_f(ptr);}}这时再查看文件内容:
虽然打印出了地址,但还是不直观,这里用到一个工具addr2line
可以通过addr2line -f -e ./memleak -a 0x5595d3a06bcc查看文件和行号
还有一种通过libc的方式,更底层的方式,因为malloc底层调用的是__libc_malloc:
externvoid*__libc_malloc(size_tsize);externvoid__libc_free(void*ptr);intenable_malloc_hook=1;intenable_free_hook=1;void*malloc(size_tsize){void*ptr=NULL;if(enable_malloc_hook){enable_malloc_hook=0;printf("malloc \n");ptr=__libc_malloc(size);//0参数表示上一级 1向上反二级//main -> f1() -> f2() -> f3() {}void*caller=__builtin_return_address(0);charfilename[128]={0};sprintf(filename,"./mem/%p.mem",ptr);FILE*fp=fopen(filename,"w");fprintf(fp,"[+]caller: %p, addr: %p, size: %ld",caller,ptr,size);fflush(fp);enable_malloc_hook=1;}else{ptr=__libc_malloc(size);}returnptr;}voidfree(void*ptr){if(enable_free_hook){enable_free_hook=0;charbuff[128]={0};sprintf(buff,"./mem/%p.mem",ptr);if(unlink(buff)<0){printf("double free: %p\n",ptr);return;}__libc_free(ptr);enable_free_hook=1;}else{__libc_free(ptr);}}try-catch
#include<stdio.h>#include<setjmp.h>jmp_buf env;voidfunc(intidx){printf("func --> idx: %d\n",idx);longjmp(env,idx);}intmain(){intcount=setjmp(env);if(count==0){printf("count -> %d\n",count);func(++count);}elseif(count==1){printf("count -> %d\n",count);func(++count);}elseif(count==2){printf("count -> %d\n",count);func(++count);}elseif(count==3){printf("count -> %d\n",count);func(++count);}printf("other count : %d\n",count);return0;}程序输出:
count -> 0 func --> idx: 1 count -> 1 func --> idx: 2 count -> 2 func --> idx: 3 count -> 3 func --> idx: 4 other count : 4#include<stdio.h>#include<setjmp.h>#defineTRYintcount=setjmp(env);if(count==0)#defineCATCH(x)elseif(count==(x))#defineTHROW(idx)longjmp(env,(idx))#defineFINALLYjmp_buf env;voidfunc(intidx){printf("func --> idx: %d\n",idx);THROW(idx);}intmain(){TRY{printf("count -> %d\n",count);func(++count);}CATCH(1){printf("count -> %d\n",count);func(++count);}CATCH(2){printf("count -> %d\n",count);func(++count);}CATCH(3){printf("count -> %d\n",count);func(++count);}FINALLY{printf("other count : %d\n",count);}return0;}但同时存在两个问题:
- try/catch嵌套
用单链表头插法 - 线程安全问题
pthread_key_t
文章参考于<零声教育>的C/C++linux服务期高级架构系统教程学习:https://ke.qq.com/course/417774?flowToken=1020253