一. 缘起

年前,写了使用mtrace定位内存泄漏,在留言中,有读者提到了希望介绍valgrind,那好,今天就介绍使用valgrind定位内存泄漏。

大约2-3年前,杨同学让我帮做模拟面试,他求职的是C++岗位,我问了这样一个问题:在你的项目中,你是如何定位内存泄漏的呢?

结果,他对这个问题很陌生,感觉从来没有思考过相关问题,也没有做这方面的准备,自然就没法正确作答,这让我觉得有点吃惊。

如果准备得不成功,那就要准备失败了。在笔试面试中,遇到内存泄漏的定位问题,如果连valgrind都说不出来,那就很容易歇菜了。

总之,无论是为了找工作,还是为了实际工作中的问题,都很有必要熟练使用valgrind,那么,我们一起来看看这玩意儿是怎么回事。

 

二. valgrind简介

有的朋友可能还不熟悉linux, 没关系,和涛哥一起来,先来man valgrind一下:

  •  
ubuntu@VM-0-15-ubuntu:~$ man valgrindVALGRIND(1)                                           Release 3.11.0                                          VALGRIND(1)
NAME valgrind - a suite of tools for debugging and profiling programs
SYNOPSIS valgrind [valgrind-options] [your-program] [your-program-options]
DESCRIPTION Valgrind is a flexible program for debugging and profiling Linux executables. It consists of a core, which provides a synthetic CPU in software, and a series of debugging and profiling tools. The architecture is modular, so that new tools can be created easily and without disturbing the existing structure.
Some of the options described below work with all Valgrind tools, and some only work with a few or one. The section MEMCHECK OPTIONS and those below it describe tool-specific options.
This manual page covers only basic usage and options. For more comprehensive information, please see the HTML documentation on your system: $INSTALL/share/doc/valgrind/html/index.html, or online: http://www.valgrind.org/docs/manual/index.html.
TOOL SELECTION OPTIONS The single most important option.
--tool=<toolname> [default: memcheck] Run the Valgrind tool called toolname, e.g. memcheck, cachegrind, callgrind, helgrind, drd, massif, lackey, none, exp-sgcheck, exp-bbv, exp-dhat, etc.

 

可以看到,valgrind是一款程序调试和分析的工具,其最重要的功能是内存检查,而我们今天要介绍的内存泄漏检查,便是其一。

valgrind除了检查内存泄漏外,还能检查内存越界、内存异常释放等诸多问题。这也是当年在腾讯面试中,面试官补充提到的问题。

具体来说,就是可以通过valgrind的toolname参数,来定制使用valgrind功能。下面,一起来看下toolname参数的部分选项。

 

  • memcheck: 检查内存问题,如泄漏、越界、异常释放

  • callgrind:    分析程序性能

  • cachegrind: 分析cache

  • helgrind:     分析多线程竞争

  • massif:        分析堆

接下来,我们介绍使用valgrind的toolname参数中的memcheck, 并用实际例子来看看如何定位内存泄漏。

 

三. 用valgrind定位内存泄漏

开发人员都应该知道,内存泄漏是一个很严重的问题,不容忽视。说起来有点悲催,我大学毕业时,居然还没听说过内存泄漏。

往事不堪回首,那就不回首了。言归正传,进入正题。我们来写一段有内存泄漏的程序,如下(只有malloc, 没有free)

  •  
#include <stdio.h>#include <stdlib.h>char* getMemory(){  char *p = (char *)malloc(30);   return p;} int main(){  char *p = getMemory();  p = NULL;    return 0;}

 

很明显,这段程序存在内存泄漏。有的读者总要说,一眼就看出来的问题,你用valgrind整那么复杂干啥?

很显然,这种同学缺乏实战经验,对敌人没有清醒的认识。几十万行的代码,你能用肉眼很明显看出内存泄漏?

也有同学要说,先申请堆内存,赋值给p指针, 然后又不使用p指针, 这不是无聊吗(一位读者之前提的问题)?

同学,实际项目中肯定要使用p啊!而在本文中,我只是用一个demo例子,来实战说明内存泄漏及其定位方法。

 

话不多说,继续介绍valgrind吧!先来编译一下(注意:编译时带上-g参数,便于定位出具体的代码行),然后用valgrind分析:

  •  
ubuntu@VM-0-15-ubuntu:~$ g++ -g test.cppubuntu@VM-0-15-ubuntu:~$ubuntu@VM-0-15-ubuntu:~$ubuntu@VM-0-15-ubuntu:~$ valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./a.out==31560== Memcheck, a memory error detector==31560== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.==31560== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info==31560== Command: ./a.out==31560== ==31560== ==31560== HEAP SUMMARY:==31560==     in use at exit: 30 bytes in 1 blocks==31560==   total heap usage: 1 allocs, 0 frees, 30 bytes allocated==31560== ==31560== 30 bytes in 1 blocks are definitely lost in loss record 1 of 1==31560==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)==31560==    by 0x400537: getMemory() (test.cpp:5)==31560==    by 0x40054E: main (test.cpp:11)==31560== ==31560== LEAK SUMMARY:==31560==    definitely lost: 30 bytes in 1 blocks==31560==    indirectly lost: 0 bytes in 0 blocks==31560==      possibly lost: 0 bytes in 0 blocks==31560==    still reachable: 0 bytes in 0 blocks==31560==         suppressed: 0 bytes in 0 blocks==31560== ==31560== For counts of detected and suppressed errors, rerun with: -v==31560== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Oh, so nice啊,可以清晰地看到,内存definitely lost了,并且知道是getMemory函数中,且在第5行,于是乎,找到了内存泄漏的地方了。

接下来,我们准备修复这个内存泄漏问题,然后使用valgrind进行再次验证。

 

四. 修复后再验证

修复内存泄漏后,代码为:

  •  
#include <stdio.h>#include <stdlib.h>char* getMemory(){  char *p = (char *)malloc(30);   return p;} int main(){  char *p = getMemory();  if(p != NULL)  {    free(p);    p = NULL;  }    return 0;}

 

编译一下,并再次使用valgrind来验证:

  •  
ubuntu@VM-0-15-ubuntu:~$ g++ -g test.cppubuntu@VM-0-15-ubuntu:~$ubuntu@VM-0-15-ubuntu:~$ubuntu@VM-0-15-ubuntu:~$ valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./a.out==2839== Memcheck, a memory error detector==2839== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.==2839== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info==2839== Command: ./a.out==2839== ==2839== ==2839== HEAP SUMMARY:==2839==     in use at exit: 0 bytes in 0 blocks==2839==   total heap usage: 1 allocs, 1 frees, 30 bytes allocated==2839== ==2839== All heap blocks were freed -- no leaks are possible==2839== ==2839== For counts of detected and suppressed errors, rerun with: -v==2839== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)ubuntu@VM-0-15-ubuntu:~$

快来看啊,All heap blocks were freed -- no leaks are possible, 所有的堆内存都释放了,不存在任何内存泄漏。看到这句话后,总算是心安了。

 

五. 最后的话

关于内存泄露的问题,我们已经介绍过mtrace和valgrind两种工具来调试和定位,也欢迎大家分享自己使用过的有效方法。

风在起,云在涌,心情在跳动。金三银四,已经开始。祝大家工作顺利,祝跳槽的朋友,薪资翻倍,offer多多,挑得花眼。

·················· END ··················