Valgrind(一) valgrind的基本使用

Memcheck

默认的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc、free、new、delete的调用都会被捕获。所以,它能检测以下问题:

  1. 对未初始化内存的使用;

  2. 读/写释放后的内存块;

  3. 读/写超出malloc分配的内存块;

  4. 读/写不适当的栈中内存块;

  5. 内存泄漏,指向一块内存的指针永远丢失;

  6. 不正确的malloc/free或new/delete匹配;

  7. memcpy()相关函数中的dst和src指针重叠

valgrind只报告内存泄漏,但没有显示具体代码中泄漏的地方。因此需要使用--leak-check=full选项启动valgrind。你的程序将比正常的运行慢得多(如20〜30倍),并使用更多的内存。Memcheck会报告关于内存错误,和检测到的泄漏的消息。

Memcheck也报告未初始化值的使用,最常见的消息是Conditional jump or move depends on uninitialised value(s)。可以难以确定这些错误的根源。尝试使用--track-origins=yes来获得额外的信息。这使得Memcheck运行得更慢,

Invalid write of size 1 //堆内存越界被查出来

Invalid free() / delete / delete[] //重复释放

Process terminating with default action of signal 11 (SIGSEGV): dumping core //非法指针,导致coredump

massif

堆栈分析器,指示程序中使用了多少堆内存等信息。它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。

Massif对内存的分配和释放做profile。程序开发者通过它可以深入了解程序的内存使用行为,从而对内存使用进行优化。这个功能对C++尤其有用,因为C++有很多隐藏的内存分配和释放

memcheck的常用选项

—tool= [default: memcheck]

最常用的选项。运行valgrind中名为toolname的工具。如果省略工具名,默认运行memcheck。

—leak-check= [default: summary]

用于控制内存泄漏检测力度。

  • no,不检测内存泄漏;

  • summary,仅报告总共泄漏的数量,不报告具体泄漏位置;

  • yes/full,报告泄漏总数、泄漏的具体位置

—show-reachable= [default: no]

用于控制是否检测控制范围之外的泄漏,比如全局指针、static指针等。

—undef-value-errors= [default: yes]

用于控制是否检测代码中使用未初始化变量的情况。

—log-file=filename # 将结果输出到文件,支持相对路径

—track-fds= [default: no] # 跟踪打开的文件描述符

五种泄露内存的类型

  • definitely lost:确认丢失。程序中存在内存泄露,应尽快修复。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存则会报这个错误。
  • indirectly lost:间接丢失。当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可。例子可参考我的例程。

  • possibly lost:可能丢失。大多数情况下应视为与”definitely lost”一样需要尽快修复,除非你的程序让一个指针指向一块动态分配的内存(但不是这块内存起始地址),然后通过运算得到这块内存起始地址,再释放它。例子可参考我的例程。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存的起始地址,但可以访问其中的某一部分数据,则会报这个错误。

  • still reachable:可以访问,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃,但长时间运行有可能耗尽系统资源,因此笔者建议修复它。如果程序是崩溃(如访问非法的地址而崩溃)而非正常结束的,则应当暂时忽略它,先修复导致程序崩溃的错误,然后重新检测。

  • suppressed:已被解决。出现了内存泄露但系统自动处理了。可以无视这类错误。这类错误我没能用例程触发,看官方的解释也不太清楚是操作系统处理的还是valgrind,也没有遇到过。

操作MySQL结果集后释放内存

代码中获取MySQL结果常用由mysql_store_result,结果用valgrind检查时发现了内存泄露问题:

由mysql_store_result()、mysql_use_result()、mysql_list_dbs()获得结果集,在完成对结果集的操作后,必须调用mysql_free_result()释放结果集使用的内存。每次查询返回的结果的地址是不一样的,所以每次都要释放,否则会造成内存泄露。

mysql_free_result 的危害太大,目前造成了很多问题,在将获取的SQL结果插入到容器时,出现了很多乱码和不正常的字符串.如果没有明显内存泄露,不再加这句命令.

几种内存泄露的情况

mysql_init内存泄露

再次用valgrind检查操作MySQL的代码,发现还有一个泄露的情况:

一般在使用MySQL结束后,会调用mysql_close,但是这样解决不了这个泄露情况,应当调用mysql_library_end()释放 剩余的内存空间。所以MySQL的最后经常是:

1
2
3
mysql_free_result(result);
mysql_close(conn);
mysql_library_end();

如果是在类中使用MySQL,一般是把mysql_closemysql_library_end()放在析构函数里。

跨线程释放内存

我在类中使用了一个指针,打算在析构函数里释放其指向内存,编译运行都正常,但是用valgrind发现了问题:

内存地址在线程1的stack上,看代码发现这个指针的内存确实不是在主线程,所以不要在主线程上释放,否则提示free invalid

log4cpp::PropertyConfigurator::configure的内存泄露

ros::NodeHandle的内存泄露

在Kinetic的ros::NodeHandle源码里,一个指针没有delete就置为NULL,目前的melodic没有了这个bug,不必自己解决了。