1. 引言
Valgrind的工具套件提供了大量的调试和分析工具,帮助你让你的程序跑的更快,更正确。这些工具中最流行的被称为Memcheck。它可以检测C和C++程序中常见的许多和内存相关的,并可能导致崩溃和不可预知行为的错误。
本指南的其余部分提供了你所需要的最少信息来开始使用Memcheck程序检测内存错误。对于Memcheck和其他工具的完整文档,请阅读产品说明书。
2. 准备程序
用-g编译你的程序来包含调试信息,从而Memcheck的错误消息包括确切的行号。如果你能容忍速度放缓,使用-O0也是一个好主意。使用-O1的话,错误信息的行号可能是不准确的,但总体上说,在用-O1编译后的代码上运行Memcheck 工作还算不错,而且与运行-O0相比,速度提升相当显著。不建议使用 -O2及以上编译选项,因为Memcheck会偶尔报告实际上不存在的未初始化值错误。
3. 在Memcheck下运行你的程序
如果你如下正常运行程序:
myprog arg1 arg2
使用此命令行:
valgrind --leak-check=yes myprog arg1 arg2
Memcheck是默认的工具。--leak-check
选项打开详细的内存泄漏检测器。
你的程序将比正常的运行慢得多(如20〜30倍),并使用更多的内存。Memcheck会报告关于内存错误,和检测到的泄漏的消息。
4. 解析Memcheck的输出
下面是一个名为 a.c 的C程序示例,包含一个内存错误和内存泄漏。
#include <stdlib.h>
void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0; // problem 1: heap block overrun
} // problem 2: memory leak -- x not freed
int main(void)
{
f();
return 0;
}
大多数的错误信息看起来像下面的问题1堆块溢出的描述:
==19182== Invalid write of size 4
==19182== at 0x804838F: f (example.c:6)
==19182== by 0x80483AB: main (example.c:11)
==19182== Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd
==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)
==19182== by 0x8048385: f (example.c:5)
==19182== by 0x80483AB: main (example.c:11)
要注意事项:
- 关于每个错误消息有很多的信息; 仔细阅读。
- 19182是进程ID; 它通常是不重要的。
- 第一行(“Invalid write...”)告诉你它是什么样的错误。这里,程序写到一些因堆块溢出而不应该写的内存上。
- 第一行的下面是一个堆栈跟踪来告诉您发生问题的地方。堆栈跟踪可以变得相当大,而且会造成混淆,尤其是如果你使用C++ STL的时候。从下往上看它们可以提供一些帮助。如果堆栈跟踪不够大,使用--num-callers来使之变得更大。
- 代码的地址(如0x804838F)通常并不重要,但对于跟踪怪异的bug偶尔至关重要。
- 某些错误消息有第二个部分来描述所涉及到的内存地址。上述说明了内存写的位置越过了在example.c的第5行使用malloc()分配的块的末尾处。
后来的错误可能是由先前的错误导致的。如果不这样做,这是Memcheck难于使用的一个常见原因。
内存泄漏的消息是这样的:
==19182== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)
==19182== by 0x8048385: f (a.c:5)
==19182== by 0x80483AB: main (a.c:11)
堆栈跟踪告诉你泄漏的内存在哪里被分配。很遗憾,Memcheck不能告诉你为什么内存泄漏了。(忽略“vg_replace_malloc.c”,这是一个实现细节。)
有几种类型的泄漏; 最重要的两个类型是:
- “肯定丢失”:你的程序的内存正在泄漏 - 修复它!
- “可能丢失”:你的程序正在泄漏内存,除非你正在使用指针做有趣的事情(如移动它们指向的堆块的中间)。
Memcheck也报告未初始化值的使用,最常见的消息是"Conditional jump or move depends on uninitialised value(s)"。可以难以确定这些错误的根源。尝试使用--track-origins=yes
来获得额外的信息。这使得Memcheck运行得更慢,但你得到的额外的信息常常节省了大量的时间去搞清楚了未初始化值是从哪里来的。
5. 注意事项
Memcheck是不完美的; 它有时会产生误报,并有抑制这些误报的机制。但是,它在99%的时候通常是正确的,所以你应该警惕忽视了错误信息。毕竟,你不会不理警告由编译器生成的消息,对不对?如果Memcheck报告库代码中的错误,而这些代码是你不能改变的,那么抑制机制也是有用的。默认的抑制设置隐藏了很多这样的错误,但是你可能会遇到更多。
Memcheck无法检测到你的程序的每一个内存错误。例如,它不能检测出超出范围的数组读取或写入,如果这些数组是静态分配或在栈上分配。但它能检测出许多错误,这些错误可能使你的程序崩溃(例如导致段错误)。
尽量使你的程序干净,不让Memcheck报告任何错误。一旦你达到这样的状态,当程序改变而使Memcheck报告新的错误时,很容易发现问题。这几年Memcheck的使用经验表明,甚至能够使巨大的程序运行Memcheck而没有报告任何错误。举例来说,KDE,OpenOffice.org和Firefox的大部分都是Memcheck-clean,或者非常的接近。
6. 更多信息