Valgrind Memcheck
一、Valgrind Memcheck
有关 Memcheck 和其他工具的完整文档,请阅读《用户手册》,这里知识简单介绍如何基础的使用Memcheck。
二、准备
编译程序时使用-g选项,这样Memcheck的错误信息就能包含准确的行号。
如果你觉得编译速度没关系的话,使用-Oo选项也是好的。
使用 -O1 时,错误信息中的行号可能会不准确,不过一般来说,在以 -O1 编译的代码上运行 Memcheck 时效果相当不错,与运行 -O0 时相比,速度提升相当显著。不建议使用 -O2 或更高版本,因为 Memcheck 有时会报告实际上并不存在的未初始化值错误。
三、在Memcheck下运行你的程序
比如,像这样程序运行:
myprog arg1 arg2
使用如下命令就可以检查你的程序内存泄漏问题
valgrind --leak-check=yes myprog arg1 arg2
Memcheck 是默认工具。使用 --leak-check 选项可打开详细的内存泄漏检测器。
你的程序运行速度会比正常情况下慢很多(例如 20 到 30 倍),而且会占用更多内存。Memcheck 会就其检测到的内存错误和泄露信息发出来
四、解读Memcheck的输出结果
下面是一个C程序,文件为test.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;
}
4.1、内存溢出错误信息
运行valgrind的内存检查,错误信息如下:一个内存溢出的错误(heap block overrun)描述
==19182== Invalid write of size 4
==19182== at 0x804838F: f (test.c:5)
==19182== by 0x80483AB: main (test.c:10)
==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)
4.1.1、需要注意的事项
- 每条错误信息都包含大量信息,请仔细阅读。
- 19182 是进程 ID,通常并不重要。
- 第一行(“Invalid write…”)告诉你是哪种错误。在这里,由于堆块超限,程序写入了一些不应该写入的内存。
- 首行下面是堆栈跟踪,告诉你问题发生在哪里。堆栈跟踪可能会变的很大,而且容易混淆,尤其是在使用C++ STL的情况下,从下往上读堆栈跟踪是可以帮助我们梳理问题。如果堆栈跟踪不够大,可以使用–num-callers选项将其变大。
- 如 0x804838F这样的地址,通常并不重要,但有时对追踪很奇怪的错误会有帮助。
- 有些错误信息的第二个部分描述了所涉及的内存地址。这条信息显示,写入的内存刚过 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 (test.c:5)
==19182== by 0x80483AB: main (test.c,:10)
堆栈跟踪会告诉你泄露的内存是在哪里分配的。遗憾的是,Memcheck 无法告诉你内存泄漏的原因。(忽略 “vg_replace_malloc.c”,那是实现细节)。
4.2、泄漏有多种类型,其中最重要的有两类:
- “definitely lost”: your program is leaking memory – fix it!
- “probably lost”:your program is leaking memory, unless you’re doing funny things with pointers (such as moving them to point to the middle of a heap block).
Memcheck也会报告未初始化值的使用情况,常见的信息是"Conditional jump or move depends on uninitialised value(s)"。要确定这些错误的根本原因可能很困难。尝试使用 --track-origins=yes 获取更多信息。这虽然会降低 Memcheck 的运行速度,但获得的额外信息往往可以节省大量时间,从而找出未初始化值的来源。
如果你不理解错误信息,请查阅Valgrind用户手册中的Memcheck错误信息解释,其中有Memcheck生成的所有错误信息的示例。
五、注意事项
Memcheck 并不完美;它偶尔会产生误报,但也有抑制误报的机制(请参阅《Valgrind 用户手册》中的抑制错误)。不过,它通常 99% 的时间都是正确的,所以你应该警惕忽视它的错误信息。毕竟,你不会忽略编译器生成的警告信息,不是吗?如果 Memcheck 报告了你无法更改的库代码中的错误,抑制机制也很有用。默认的抑制设置会隐藏很多这样的错误,但你可能会遇到更多。
Memcheck 无法检测程序的所有内存错误。例如,它无法检测对静态分配或堆栈上的数组的超范围读取或写入。但它应该能检测出许多可能导致程序崩溃的错误(如导致分段故障)。
尽量使你的程序干净到 Memcheck 不报错。一旦达到这种状态,当程序发生变化导致 Memcheck 报告新错误时,就更容易发现了。多年使用 Memcheck 的经验表明,即使是庞大的程序,也有可能以干净的方式运行 Memcheck。例如,KDE、OpenOffice.org 和 Firefox 中的大部分程序都是 Memcheck-clean,或非常接近 Memcheck-clean。
六、更多信息
请查阅 Valgrind 常见问题和 Valgrind 用户手册,其中有更多信息。请注意,Valgrind 发行版中的其他工具可以通过 --tool 选项调用。