前言
关于The Valgrind Quick Start Guide 说明
之所以我要翻译的这篇文章,主要有两个目的。第一是加深我对Valgrind 工具的理解。第二方便后期学习和复习,以及对中文社区作出一点自己的贡献(尽管我发现在CSDN等社区,已经有了)
来源以及说明
这篇文章是对 的中文翻译。The Valgrind Quick Start Guide 是给出的关于Valgrind 工具指导内容。
1. 介绍
Valgrind 工具套件提供了许多调试和分析工具,可帮助您使程序更快、更正确。这些工具中最受欢迎一个被称之为 Memcheck。它可以检测 C 和 C++ 程序中常见与内存相关的错误,这些错误可能导致崩溃和不可预知的行为()。
本指南的内容包含使用 Memcheck 检测程序检测内存错误所需的最少需要了解的信息。关于Memcheck 和其他工具的完整文档,请阅读用户手册。
2. 准备一个程序
使用-g编译程序来包含调试信息,以便 Memcheck 的错误消息包含准确的行号。如果您可以容忍执行过程中减速,使用 -O0 也是一个好主意。使用-O1编译,有可能导致错误消息中的行号可能不准确。一般来说在编译的代码时候使用-O1编译,对于运行 Memcheck效果比较好 ,并且与运行速度相对于-O0提升明显。不建议使用 -O2及以上,因为 Memcheck 偶尔会报告实际上并不存在的未初始化值错误。
总结
- 使用
-g来编译程序,来使用Memcheck ,使输出的错误消息可以包含准确的行号 - 编译时候开启
-O0在运行的过程中比较慢,所以这对于较大的程序来说是不友好的 - 编译的时候开启
-O1优点是对于Memcheck 执行效果比较好,同时速度比-O0更快。缺点是输出的错误信息,行号可能不准确(理解为不利于定位错误点) - 开启
-O2以及以上,这是不推荐的,因为可能会报告==未初始化值错误==
3. 在Memcheck下运行程序
如果程序是这样运行的(C/C++ 程序执行)
myprog arg1 arg2应该使用这样的命令行
valgrind --leak-check=yes myprog arg1 arg2--leak-check 表示内存检查。命令表示:使用valgrind 工具对程序myporg 传入参数arg1 arg2 开始运行,在运行过程开启内存泄漏的检查。
程序会比正常运行的时候慢(比如:慢20倍或者30倍)并且会使用更多的内存。在执行完后,Memcheck 会发布有关检测到的内存错误和泄漏的消息。
4. 内存泄漏检测输出结果
这是一个C案例程序,文件名==a.c== 它包含内存错误(memory error)和内存泄漏(memory leak)
void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0; // problem 1: 堆分配内存块溢出 heap block overrun
} // problem 2: memory leak -- x 内存没有被free 释放掉
int main(void)
{
f();
return 0;
}大多数错误信息如下所示,描述了问题 1,堆块溢出(heap block overrun)
==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是程序的进程编号(Process ID ),通常不太重要- 第一行("Invalid write...")告诉你错误的类型。这里是,因为堆块溢出,程序写入了一些不该写入的内存地址。
- 在第一行的下面,是一个堆栈追踪(stack track),告知程序出现错误的位置。堆栈跟踪可能会变得非常大,并且是令人困惑的(why?),尤其是使用了C++ STL 的时候。如果堆栈跟踪内存不是足够的(详细),使用
--num-callers选项会使得更大。 - 代码地址(例如 0x804838F)通常不重要,但有时对于追踪特别奇怪的错误至关重要。
- 有些错误消息有第二个部分,它描述了所涉及的内存地址。比如:写入的内存刚刚超过在 example.c 的第 5 行使用 malloc() 分配的块的末尾(
Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd)。
按照报告的顺序修复错误是值得的,因为后面的错误可能是由早期的错误引起的。未能做到这一点是 Memcheck 遇到困难的常见原因。
注入:valgrind 错误解决方式,需要从头到尾按照顺序来解决。因为:前面错误会导致后面的错误的出现。不按照顺序的话,在解决错误的时候,会出现一些问题。遇到困难
内存泄漏(memory leak )消息如下所示:
==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)堆栈跟踪会告诉您泄漏内存的==分配位置==(allocated )。不幸的是,Memcheck 无法告诉您内存泄漏的原因。(Ignore the "vg_replace_malloc.c", that's an implementation detail.)忽略“vg_replace_malloc.c”,这是一个实现细节。
有几种泄漏;两个最重要的类别是:
- "definitely lost": your program is leaking memory -- fix it!
- "probably lost":您的程序正在泄漏内存,除非您正在使用指针做一些有趣的事情(例如将它们移动到指向堆块的中间)。
Memcheck 还报告==未初始化值(uses of uninitialised values)==的使用,最常见的是“条件跳转或移动取决于未初始化的值("Conditional jump or move depends on uninitialised value(s))”。很难确定这些错误的根本原因。尝试使用 --track-origins=yes 来获取额外信息,可能会导致Memcheck 运行速度变慢,但获得的额外信息通常可以节省大量时间来确定未初始化值的来源。
如果您不理解错误消息,请参阅 中 其中包含 Memcheck 产生的所有错误消息的示例。
5. 注意事项
Memcheck 并不完善;它偶尔会产生误报,并且有机制来抑制这些错误误报情况(请参阅 中的)。它通常在 99% 的情况下都是正确的,因此您应该小心忽略它的错误消息。毕竟,您不会忽略编译器生成的警告消息,对吧?如果 Memcheck 报告了许多在库代码中错误,这些错误无法更改,通常抑制机制可以有效的抑制这类消息。默认的抑制设置隐藏了很多这种错误。我们常常会遇到这些,但通常Memecheck 会将这些错误隐藏掉。
Memcheck 无法检测您的程序存在的每个内存错误。例如,它无法检测对静态分配或堆栈上的数组,发生超出范围的读取或写入的情况。但它应该检测到许多可能使您的程序崩溃的错误(例如,产生分段错误segmentation fault))
试着让你的程序保持干净,也就是 Memcheck 不会报告任何错误。一旦实现Memcheck clean 状态,不输出任何错误,那么一旦程序发生了更改,就会很容易看见什么地方的更改导致Memcheck 报告错误。多年使用 Memcheck 的经验表明,即使是大型程序也可以以干净的方式运行 Memcheck。例如,KDE、OpenOffice.org 和 Firefox 的大部分都是 Memcheck-clean,或者非常接近它。
6. 更多信息
请查阅和 ,其中包含更多信息。在Valgrind 的发行版本中,有一些其他工具,我们可以通过--tool 来调用。
7.参考
总结
GCC 编译选项对于Memcheck 设置
GCC Compiler Options 补充说明
| Options | Details |
|---|---|
-O0 | explicitly turns off all optimizations. 关闭所有的优化 |
-O1 | enables a few optimizations, but isn't very useful overall. 启用了一些优化,但总体上不是很有用。 |
-O2 | is the most commonly used optimization flag.是最常用的优化标志。 |
-O3 | enable even more optimizations, but can actually harm the runtime of your code by making it take up more space (so it doesn't fit into cache anymore). |
-Os | 针对小型二进制文件进行优化。 |
总结
-O0等选项,对于编译器执行的是优化选项,但是对于程序内存检测调试来说,是有害的。Memcheck 推荐使用-O1启动了部分优化,但是不会有太多问题,只是会导致行错误不准确,之所以不关闭所有优化,是因为Memcheck 检测时候,程序会存在过慢问题。
这里很明显是一个取舍问题
程序小的话,不开启优化-O0,使用Memcheck 执行检测。程序过大的话,开启部分优化
-O1,只是会导致一些行报告错误的准确,但是会提高程序的运行效率,有利于测试。在任何情况下都不要开启
-O2及以上,过度优化,对于Memcheck 检测来说是有害的。前面错误会导致后面的错误的出现。所以我们需要从头开始一步一步解决Valgrind 报告的问题。
最常见和最多的内存泄漏错误
"definitely lost": your program is leaking memory -- fix it!
"probably lost":您的程序正在泄漏内存,除非您正在使用指针做一些有趣的事情(例如将它们移动到指向堆块的中间)。
引用的库代码存在错误的话,Memcheck 检测到后,会自己隐藏起来。因为:库代码对用户来说是不可更改的,所以并不需要这类消息。
有些内存错误情况无法报错,更多是报错
segmentation faultvalgrind 含有
除了 Memcheck,Valgrind 还有其他几个工具:[8]
None,在虚拟机中运行代码而不执行任何分析,因此在所有工具中具有最小的 CPU 和内存开销。由于 valgrind 本身提供了从分段错误的回溯,因此none工具以最小的开销提供此回溯。
Addrcheck,类似于 Memcheck,但 CPU 和内存开销要小得多,因此捕获的错误类型更少。自 3.2.0 版起,Addrcheck 已被删除。[9]
Massif,一个堆 分析器。单独的GUI massif-visualizer 可视化 Massif 的输出。
Helgrind和DRD,检测多线程代码中的竞争条件
Cachegrind,一个缓存分析器。单独的 GUI KCacheGrind 可视化来自 Cachegrind 的输出。
Callgrind是由 Josef Weidendorfer创建的调用图分析器,从 3.2.0 版开始添加到 Valgrind。
KCacheGrind 可以可视化 Callgrind 的输出。
DHAT,动态堆分析工具,可分析分配了多少内存、分配了多长时间以及内存使用模式。
exp-sgcheck(在 3.7 版之前命名为exp-ptrcheck),一个实验性工具,用于查找 Memcheck 无法找到的堆栈和全局数组溢出错误。[10]此工具的某些代码会导致误报。[11]
exp-bbv,一个性能模拟器,可以从小样本集中推断性能。