
一,Valgrind简介
Valgrind提供了很多组件,这些组件可以用来分析和调试程序、检测内存是否正常使用、分析程序的性能等。Valgrind有自己的内核,它可以提供一个虚拟的CPU来运行程序,并完成程序的调试和剖析等任务。
Valgrind被设计成非侵入式的,利用它可以直接启动可执行程序,而不需要重新编译、链接和修改可执行程序对应的源代码。
Ubuntu环境下,用下面的安装命令即可开始使用Valgrind:
apt-get install valgrind二,Valgrind主要组件介绍
1.Memcheck:内存检测组件,检测内存是否被非法使用等信息。
2.Cachegrind:缓存分析组件,检测缓存丢失和命中等信息。
3.Callgrind:函数调用分析组件,检测程序的运行时间和调用过程。
4.Massif:堆分析组件,检测程序使用了多少堆内存。
5.Helgrind:线程调试组件,检测多线程资源竞争等信息。
本文重点讲解Memcheck组件,因为它在开发中使用得最多。执行程序在Memcheck的监视下运行时,Memcheck将检查所有内存读取和写入,并截取对malloc/new/free/delete的调用。Memcheck会在错误使用内存时立即报告这些错误,并给出发生错误的源代码行号,以及与错误相关的函数的堆栈跟踪信息。
注意,Memcheck无法检测出静态分配或堆栈上的数组的超出范围读取/写入问题。
三,C++开发中常见的内存错误使用案例
(1)使用未初始化的内存,比如使用未初始化的指针。
(2)读/写已经被释放的内存。
(3)读/写内存越界,比如数组访问越界。
(4)内存覆盖,比如memcpy的src和dst指针有重叠。
(5)使用malloc/new/new[]和free/delete/delete[]不匹配。比如使用不匹配的分配和释放函数、分配了内存忘记释放、重复释放等。
(6)指针被重新赋值。
四,内存泄露检测教程
1.valgrind的调用方式:
valgrind [valgrind-options] your-prog [your-prog-options]通用的选项:
--tool=<toolname> [default: memcheck]
#选择需要使用的Valgrind组件,例如例如 memcheck、cachegrind、callgrind等
-q, --quiet
#静默运行,仅打印错误消息
-v, --verbose
#打印程序各个方面的额外信息,例如:加载的共享对象、执行进度以及各种异常警告
--trace-children=<yes|no> [default: no]
#跟踪fork产生的子进程
--trace-children-skip=patt1,patt2,...
#跳过一些子进程
--vgdb=<no|yes|full> [default: yes]
#提供"gdbserver"的相关功能
--track-fds=<yes|no|all> [default: no]
#通过gdbserver monitor命令在退出或请求时打印文件描述符列表
--log-file=<filename>
#将Valgrind调试结果发送到指定文件和错误信息相关的选项:
--demangle=<yes|no> [default: yes]
#启用/禁用C++函数名的自动解析
--error-limit=<yes|no> [default: yes]
#限制Valgrind跟踪的最多错误数量
--max-threads=<number> [default: 500]
#修改Valgrind最多可以处理的线程个数,默认为500个线程生成xtree执行树相关的选项:
--xtree-memory=none|allocs|full [none]
#生成一个执行树,当设置为allocs时,内存执行树给出当前分配的字节数和当前分配的块数。
--xtree-memory-file=<filename> [default: xtmemory.kcg.%p]
#将xtree内存报告输出到指定文件2.Memcheck组件的使用命令:
valgrind --tool=memcheck --leak-check=yes --track-origins=yes [executable-name]
官方提供的操作手册:

Memcheck常用的命令选项:
--leak-check=<no|summary|yes|full> [default: summary]
#如果设置为summary,则显示整体发生了多少泄漏。如果设置为full或yes,则每个单独的泄漏将详细显示或计为错误信息。
--show-leak-kinds=<set> [default: definite,possible]
#指定显示的内存泄漏类型。例如:--show-leak-kinds=definite,indirect,possible,reachable或--show-leak-kinds=all
--show-reachable=<yes|no>, --show-possibly-lost=<yes|no>
#提供了另一种方法来指定要显示的内存泄漏类型。 --show-reachable=yes 相当于 --show-leak-kinds=all。
--xtree-leak-file=<filename> [default: xtleak.kcg.%p]
#在指定文件中生成xtree泄漏报告
--track-origins=<yes|no> [default: no]
#控制Memcheck是否跟踪未初始化值的来源。Memcheck常见的可以检测的范围:
1.对未初始化内存的使用,检测此类问题,可以在使用时增加选项"--track-origin=yes"。
2.无效的内存访问,比如读/写释放后的内存块。
3.内存重叠,比如源内存和目标内存重叠。
4.缓冲区溢出检测。
5.不正确的malloc/free或new/delete匹配。
6.权限不足的系统调用。
3.如何看懂内存检测报告:
a.存在内存泄露时候的Memcheck结果样例:
LEAK SUMMARY:
definitely lost: 410 bytes in 8 blocks
indirectly lost: 68 bytes in 2 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 1,136 bytes in 2 blocks
suppressed: 0 bytes in 0 blocks
b.没发现内存泄露时的Memcheck结果样例:
All heap blocks were freed -- no leaks are possible内存检测报告中的信息:
==XXX==:
进程ID号。
in XXXXX:
代码地址。
HEAP SUMMARY:
程序在堆上的内存分配情况,举例:1 allocs表示分配了1次内存,0 frees表示释放了0次内存,4 bytes allocated表示分配了4个字节的内存。
LEAK SUMMARY:
内存泄露情况。
Definitely lost:
确认丢失,确认存在内存泄漏的。程序找不到这些内存块,也无法去释放它们。
Indirectly lost:
间接丢失,泄露的内存是一个指针,开发中常见的野指针问题。例如一个二叉树的根节点指针丢失了,那它的所有子节点也间接丢失。总是与"Definitely lost"一起出现,修复"Definitely lost"问题即可解决。
Still reachable:
可以访问,未丢失但也未释放内存。在程序结束前,这部分内存一直没释放,程序正常结束时,可以通过这些指针来释放内存。
possibly lost:
可能丢失,大部分情况下严重性和"Definitely lost"差不多,需要修复。
suppressed:
已被解决的,此处可忽略不看。
五,常见内测泄露检测使用案例
1.使用未初始化的变量
代码样例:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int a;
if(a)
a = a+1;
return 0;
}
运行结果:

2.内存未被释放
代码样例:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
return 0;
}
运行结果:

3.重复释放内存问题
代码样例:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
free(ptr);
free(ptr);
return 0;
}
运行结果:

4.指向已释放内存的指针
代码样例:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
free(ptr);
ptr[3] = 'a';
return 0;
}
运行结果:

5.无效的内存访问
代码样例:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
ptr[11] = 'z';
return 0;
}
运行结果:

六,Valgrind的局限性
Valgrind在对信号的模拟上,提供了POSIX版本的sigaction和sigprocmask功能实现,但是对于非POSIX版本的信号处理机制无法进行模拟。
如果开发者自定义内存管理函数,而不是使用malloc/new/free/delete,一旦内存使用出错, Memcheck组件不一定能检测出来。
在Memcheck组件下运行程序时,程序的内存消耗会大大增加,运行速度也会变慢很多。
七,参考阅读
https://valgrind.org/docs/manual/manual-core.html
https://students.cs.byu.edu/~cs235ta/labs/valgrind/valgrind.php
https://linoxide.com/valgrind-memcheck/