1.前言对于C或C++程序员来说,他们面临的bug大多是内存操作问题,而比较麻烦的是内存泄漏。虽然我们有valgrind和AScan等内存问题检测工具,但是valgrind每次输出很多,而AScan有时候看输出结果就云里雾里了。此外,谁会不喜欢工具箱中的多种工具。2.内存泄漏的一般检查2.1内存泄漏检查步骤的基本准备。做过c或者c++的同学都熟悉:首先通过top或者vmstat,或者smem(本介绍)等工具查看内存状态,看是否存在内存泄漏。其次,使用pidstat或者top指定进程,观察进程的内存使用情况。使用memleak或gdb工具查看内存泄漏。首先测试代码:#include#include#include#include#defineMALLOC_SIZE256000int*fibo(int*n0,int*n1){int*v=(int*)malloc(MALLOC_SIZE*sizeof(int));memset(v,0,MALLOC_SIZE*sizeof(int));*v=*n0+*n1;返回v;}voiddo_test(){intn0=0;诠释n1=1;int*v=NULL;诠释n=2;对于(n=2;n>0;n++){v=fibo(&n0,&n1);n0=n1;n1=*v;printf("%dth=>%lld\n",n,*v);//自由(v)睡眠(1);}}intmain(void){printf("pid=%d\n",getpid());做测试();return0;}程序比较简单,编译运行:gccmemtest.c;./a.out2.2smem工具这次使用了一个新的工具smem,是一个用python写的小工具,可以统计系统所有进程占用的物理内存RSS的工具,去掉了共享内存的PSS,以及程序本身的独占内存USS。安装:#centosyuminstallepel-releaseyuminstallsmempython-matplotlibpython-tk#ubuntuapt-getinstallsmem常用命令:-kwithunitdisplaymemoryroot@ubuntu-lab:/home/miao#smem-kPID用户命令SwapUSSPSSRSS1009root/usr/sbin/cron-f-P0304.0K399.0K2.9M1137rootnginx:masterprocess/usr/0196.0K435.0K2.1M931root/usr/sbin/irqbalance--fore0492.0K655.0K4.0M....-u-k显示每个用户的内存使用情况,单位:root@ubuntu-lab:/home/miao#smem-u-kUserCountSwapUSSPSSRSSsystemd-时间同步10764.0K1.1M6.7M消息总线10924.0K1.2M4.9M系统网络101.7M2.1M7.4M系统日志103.0M3.1M6.2Mwww数据402.0M4.2M22.4M系统-resolve104.8M5.8M12.7Mmiao8011.0M16.9M49.1Mpostgres709.2M22.0M74.5Mmysql1074.0M74.7M80.7Mroot300260.7M284.1M429.5M-w-k类似freeroot@ubuntu-lab:/home/miao#smem-w-kAreaUsedCacheNoncache固件/硬件000内核映像000内核动态内存1.5G1.3G268.5M用户空间内存414.0M191.5M222.5M空闲内存2.8G2.8G0-k跟随-suss-r非常有用,按降序显示内存使用情况root@ubuntu-lab:/home/miao#smem-k-suss-rPIDUserCommandSwapUSSPSSRSS1298root/usr/bin/dockerd-H074.3M74.5M77.9M1068mysql/usr/sbin/mariadbd074.0M74.8M80.7M939root/usr/lib/snapd/snapd044.9M45.0M46.7M....好了,基本命令介绍完了,我们来看看怎么查看内存是否泄漏,因为内存泄漏程序占用的内存一直在增加(这不是废话),所以我们将可以使用上面的排序命令只观察上面的几个过程。watchsmem-k-suss-r小技巧,在命令前加上watch,5s执行一次命令,变化的部分会高亮显示。2.3memleakcheck在ubuntu下安装memleak是非常困难的。我用的是最新的服务器版本,在centos下安装后测试:[root@xxx]#python2/usr/share/bcc/tools/memleak-p160399Attachingtopid160399,Ctrl+Ctoquit.[17:27:25]具有未完成分配的前10个堆栈:堆栈中的5个分配中的5120000字节fibo+0x1a[a.out]do_test+0x41[a.out]main+0x24[a.out]__libc_start_main+0xf5[libc-2.17.so][17:27:30]具有未完成分配的前10个堆栈:堆栈fibo+0x1a[a.out]do_test+0x41[a.out]main+0x24[a.out]__libc_start_main+0xf5[libc-2.17]中的10个分配中的10240000字节.so][17:27:35]具有未完成分配的前10个堆栈:来自堆栈的15个分配中的15360000字节fibo+0x1a[a.out]do_test+0x41[a.out]main+0x24[a.out]__libc_start_main+0xf5[libc-2.17.so][17:27:40]前10stackswithoutstandingallocations:19456000bytesin19allocationsfromstackfibo函数中存在内存泄漏,打印出泄漏的字节数。我们改了代码把free的注释去掉,然后用memleak查了一下。过了一会儿,还是没有泄漏信息,说明已经修复,如下:[root@xxx]#python2/usr/share/bcc/tools/memleak-p165349Attachingtopid165349,Ctrl+C退出。[17:35:21]未分配的前10个堆栈:[17:35:26]未分配的前10个堆栈:[17:35:31]未分配的前10个堆栈:[17:35:36]顶部10个未完成分配的堆栈:3.gdb查看内存泄漏也许你已经熟悉了memleak,让我们来看看gdb查看函数内存泄漏的方法。该方法仅用于检查特定函数是否存在内存泄漏。在某些场景下非常实用。代码中的for(n=2;n>0;n++)改为for(n=2;n>0&&n<10;n++)(gdb)bmainBreakpoint1at0x400739:文件memleaktest.c,第34行。(gdb)rStartingprogram:/home/miaohq/testcode/./a.outBreakpoint1,main()atmemleaktest.c:3434printf("pid=%d\n",getpid());缺少单独的调试信息,使用:debuginfo-installglibc-2.17-325.el7_9.x86_64(gdb)callmalloc_stats()Arena0:systembytes=0inusebytes=0Total(incl.mmap):systembytes=0inusebytes=0maxmmapregions=0maxmmapbytes=0$1=-136490560(gdb)npid=18197735do_test();(gdb)callmalloc_stats()Arena0:systembytes=0inusebytes=0Total(incl.mmap):systembytes=0inusebytes=0maxmmap区域=0maxmmap字节=0$2=-136490560(gdb)n2th=>13th=>24th=>35th=>56th=>87th=>138th=>219th=>3436return0;(gdb)callmalloc_stats()Arena0:systembytes=0inusebytes=0Total(incl.mmap):systembytes=8224768inusebytes=8224768maxmmapregions=8maxmmapbytes=8224768$3=-136490560(gdb)p256000*4*8$4=8192000(gdb)Total(incl.mmap):本程序占用的总内存,明显增加的是未释放的内存,程序增加:8224768比256000*4*8分配的内存略大。内存分配需要存储链表,并且有一些对齐的原因,所以会多分配一些free之后的场景:(gdb)callmalloc_stats()Arena0:systembytes=0inusebytes=0Total(incl.mmap):systembytes=0inusebytes=0maxmmapregions=0maxmmapbytes=0$1=-136490560(gdb)npid=18340635do_test();(gdb)n2th=>13th=>24th=>35th=>56th=>87th=>138th=>219th=>3436返回0;(gdb)调用malloc_stats()Arena0:systembytes=1159168inusebytes=0Total(incl.mmap):systembytes=1159168inusebytes=0maxmmapregions=1maxmmapbytes=1028096$2=-136490560(gdb)inusebytes为0了。