首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C/C++生态工具链——内存泄露检测工具Valgrind

C/C++生态工具链——内存泄露检测工具Valgrind

作者头像
Coder-ZZ
发布于 2023-02-23 16:45:56
发布于 2023-02-23 16:45:56
10.2K0
举报
文章被收录于专栏:C/C++进阶专栏C/C++进阶专栏

一,Valgrind简介

Valgrind提供了很多组件,这些组件可以用来分析和调试程序、检测内存是否正常使用、分析程序的性能等。Valgrind有自己的内核,它可以提供一个虚拟的CPU来运行程序,并完成程序的调试和剖析等任务。

Valgrind被设计成非侵入式的,利用它可以直接启动可执行程序,而不需要重新编译、链接和修改可执行程序对应的源代码。

Ubuntu环境下,用下面的安装命令即可开始使用Valgrind:

代码语言:javascript
复制
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的调用方式:

代码语言:javascript
复制
valgrind [valgrind-options] your-prog [your-prog-options]

通用的选项:

代码语言:javascript
复制
--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调试结果发送到指定文件

和错误信息相关的选项:

代码语言:javascript
复制
--demangle=<yes|no> [default: yes]
#启用/禁用C++函数名的自动解析

--error-limit=<yes|no> [default: yes]
#限制Valgrind跟踪的最多错误数量

--max-threads=<number> [default: 500]
#修改Valgrind最多可以处理的线程个数,默认为500个线程

生成xtree执行树相关的选项:

代码语言:javascript
复制
--xtree-memory=none|allocs|full [none]
#生成一个执行树,当设置为allocs时,内存执行树给出当前分配的字节数和当前分配的块数。

--xtree-memory-file=<filename> [default: xtmemory.kcg.%p]
#将xtree内存报告输出到指定文件

2.Memcheck组件的使用命令:

代码语言:javascript
复制
valgrind --tool=memcheck --leak-check=yes --track-origins=yes [executable-name]

官方提供的操作手册:

Memcheck常用的命令选项:

代码语言:javascript
复制
--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结果样例:

代码语言:javascript
复制
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结果样例:

代码语言:javascript
复制
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.使用未初始化的变量

代码样例:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    int a;
    if(a)
    a = a+1;
    return 0;
}

运行结果:

2.内存未被释放

代码样例:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    char *ptr = (char*)malloc(10);
    return 0;
}

运行结果:

3.重复释放内存问题

代码样例:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    char *ptr = (char*)malloc(10);
    free(ptr);
    free(ptr);

    return 0;
}

运行结果:

4.指向已释放内存的指针

代码样例:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
    char *ptr = (char*)malloc(10);
    free(ptr);

    ptr[3] = 'a';

    return 0;
}

运行结果:

5.无效的内存访问

代码样例:

代码语言:javascript
复制
#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/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档