当前位置: 首页 > news >正文

Valgrind检测内存泄漏入门指南

🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

      • `Valgrind使用指南`
        • `一、Valgrind简介`
        • `二、安装Valgrind`
        • `三、基本使用方法(Memcheck)`
          • 1. 编译程序
          • 2. 基本命令格式valgrind [valgrind选项] 程序名 [程序参数]
        • `四、核心选项详解`
          • 1. 内存泄漏检测相关
          • 2. 错误检测相关
      • 五、实战示例
      • 六、解读Valgrind输出
      • 七、高级技巧


Valgrind使用指南

一、Valgrind简介

Valgrind是一套开源工具集,核心工具包括:

  • Memcheck:检测内存管理问题(最常用)
  • Callgrind:函数调用分析和性能 profiling
  • Cachegrind:缓存使用分析
  • Helgrind:检测多线程竞争问题
  • Massif:堆内存使用分析

其中Memcheck是最常用的工具,可检测以下内存问题:

  • 使用未初始化的内存
  • 内存越界访问(数组下标越界、缓冲区溢出)
  • 内存泄漏(动态分配的内存未释放)
  • 释放后仍使用的内存(use-after-free)
  • 重复释放内存或释放非动态分配的内存
  • 不匹配的内存管理函数(如malloc搭配delete)
二、安装Valgrind

1. Linux系统(Debian/Ubuntu)sudo apt-get update

sudo apt-get install valgrind

2. Linux系统(CentOS/RHEL)

sudo yum install valgrind

3. macOS系统# 使用Homebrew

brew install valgrind

安装完成后,可通过valgrind --version验证是否安装成功。

三、基本使用方法(Memcheck)
1. 编译程序

使用Valgrind前,需用-g选项编译程序以保留调试信息,便于定位问题:

  • g++ -g -o myprogram myprogram.cpp # C++程序
  • gcc -g -o myprogram myprogram.c # C程序
2. 基本命令格式valgrind [valgrind选项] 程序名 [程序参数]

最常用的命令(检测内存泄漏和错误):

  • valgrind --leak-check=full --show-leak-kinds=all ./myprogram
四、核心选项详解
1. 内存泄漏检测相关
  • --leak-check=yes|no|full|summary
    控制是否检测内存泄漏,full会显示每个泄漏的详细信息,summary只显示汇总信息(默认是summaryleak:泄漏的意思

  • --show-leak-kinds=kind1,kind2,...
    指定显示哪些类型的泄漏,可选值:

    • definite:确定的内存泄漏(必须关注)
    • indirect:间接泄漏(由其他泄漏导致)
    • possible:可能的泄漏
    • reachable:可访问但未释放的内存
    • all:显示所有类型
  • --leak-resolution=low|med|high
    控制泄漏报告的详细程度,high会区分更多相似的泄漏

2. 错误检测相关
  • --track-origins=yes|no
    跟踪未初始化内存的来源,yes会显示更详细的信息(有助于定位使用未初始化变量的问题)

  • --vgdb=yes|no|full
    启用Valgrind的GDB服务器功能,可结合GDB调试内存错误

  • --log-file=filename
    将输出重定向到文件,避免终端输出过多

  • --suppressions=filename
    加载抑制文件,忽略已知的无害内存错误(如某些库的内部泄漏)

五、实战示例

#include <iostream>
#include <cstdlib>  // 包含malloc/free函数声明using namespace std;// 函数声明 - 每个内存问题场景用单独函数演示,结构更清晰
void demonstrate_out_of_bounds();       // 1. 数组越界访问
void demonstrate_uninitialized_memory(); // 2. 使用未初始化内存
void demonstrate_mismatched_free();     // 3. 内存释放函数不匹配
void demonstrate_double_free();         // 4. 重复释放内存
void demonstrate_memory_leak();         // 5. 内存泄漏
void demonstrate_use_after_free();      // 6. 释放后使用内存int main() {cout << "=== 开始演示各种内存问题 ===" << endl << endl;// 按顺序演示各个内存问题场景demonstrate_out_of_bounds();demonstrate_uninitialized_memory();demonstrate_mismatched_free();demonstrate_double_free();demonstrate_memory_leak();demonstrate_use_after_free();cout << endl << "=== 所有场景演示完毕 ===" << endl;return 0;
}// 1. 数组越界访问 (访问了31个元素,而实际只分配了30个)
void demonstrate_out_of_bounds() {cout << "【场景1:数组越界访问】" << endl;int* p = new int[30];  // 分配30个int的数组(索引0-29)// 正确初始化30个元素for(int i = 0; i < 30; ++i) {*(p + i) = i;}// 越界访问:i <= 30 会访问到索引30,超出范围cout << "越界输出: ";for(int i = 0; i <= 30; ++i) {cout << *(p + i) << " ";}cout << endl << endl;delete[] p;  // 正确释放(与new[]匹配)
}// 2. 使用未初始化内存 (malloc分配的内存未初始化就使用)
void demonstrate_uninitialized_memory() {cout << "【场景2:使用未初始化内存】" << endl;// malloc分配40字节(10个int),但未初始化int* p3 = (int*)malloc(40);cout << "未初始化内存输出: ";for(int i = 0; i < 10; ++i) {cout << *(p3 + i) << " ";  // 使用未初始化的内存}cout << endl << endl;free(p3);  // 正确释放(与malloc匹配)
}// 3. 内存释放函数不匹配 (new[]分配的内存用free释放)
void demonstrate_mismatched_free() {cout << "【场景3:释放函数不匹配】" << endl;int* p = new int[30];  // 使用new[]分配// 错误释放:new[]分配的内存应该用delete[]释放,而非freefree(p);  cout << "已执行错误释放(new[] + free)" << endl << endl;
}// 4. 重复释放内存 (同一内存块被释放两次)
void demonstrate_double_free() {cout << "【场景4:重复释放内存】" << endl;char* pc = new char[10];  // 分配char数组delete[] pc;  // 第一次释放(正确)delete[] pc;  // 第二次释放(错误:重复释放)cout << "已执行重复释放操作" << endl << endl;
}// 5. 内存泄漏 (malloc分配的内存未释放)
void demonstrate_memory_leak() {cout << "【场景5:内存泄漏】" << endl;// malloc分配40字节内存,但未释放(函数结束后指针p2销毁,内存无法访问)int* p2 = (int*)malloc(40);cout << "已分配内存但未释放(内存泄漏)" << endl << endl;
}// 6. 释放后使用内存 (内存被释放后继续写入数据)
void demonstrate_use_after_free() {cout << "【场景6:释放后使用内存】" << endl;int* a = new int;  // 分配int*a = 10;           // 初始化delete a;          // 释放内存*a = 20;           // 错误:使用已释放的内存cout << "已执行释放后使用内存的操作" << endl << endl;
}

执行指令:valgrind --leak-check=full --show-leak-kinds=all ./process

输出:

root@hcss-ecs-a368:~/dataDir/ValgrindTest# valgrind --leak-check=full --show-leak-kinds=all ./process
==4474== Memcheck, a memory error detector
==4474== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4474== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==4474== Command: ./process
==4474== 
=== 开始演示各种内存问题 ===【场景1:数组越界访问】
==4474== Invalid read of size 4
==4474==    at 0x1093DA: demonstrate_out_of_bounds() (main.cc:42)
==4474==    by 0x1092D2: main (main.cc:18)
==4474==  Address 0x4dd6138 is 0 bytes after a block of size 120 alloc'd
==4474==    at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109373: demonstrate_out_of_bounds() (main.cc:32)
==4474==    by 0x1092D2: main (main.cc:18)
==4474== 
越界输出: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 0 【场景2:使用未初始化内存】
==4474== Conditional jump or move depends on uninitialised value(s)
==4474==    at 0x4991A4E: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x49A0119: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x1094DA: demonstrate_uninitialized_memory() (main.cc:57)
==4474==    by 0x1092D7: main (main.cc:19)
==4474== 
==4474== Use of uninitialised value of size 8
==4474==    at 0x499192B: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x4991A78: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x49A0119: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x1094DA: demonstrate_uninitialized_memory() (main.cc:57)
==4474==    by 0x1092D7: main (main.cc:19)
==4474== 
==4474== Conditional jump or move depends on uninitialised value(s)
==4474==    at 0x499193D: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x4991A78: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x49A0119: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x1094DA: demonstrate_uninitialized_memory() (main.cc:57)
==4474==    by 0x1092D7: main (main.cc:19)
==4474== 
==4474== Conditional jump or move depends on uninitialised value(s)
==4474==    at 0x4991AAE: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x49A0119: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==4474==    by 0x1094DA: demonstrate_uninitialized_memory() (main.cc:57)
==4474==    by 0x1092D7: main (main.cc:19)
==4474== 
未初始化内存输出: 0 0 0 0 0 0 0 0 0 0 【场景3:释放函数不匹配】
==4474== Mismatched free() / delete / delete []
==4474==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109584: demonstrate_mismatched_free() (main.cc:70)
==4474==    by 0x1092DC: main (main.cc:20)
==4474==  Address 0x4dd61f0 is 0 bytes inside a block of size 120 alloc'd
==4474==    at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109574: demonstrate_mismatched_free() (main.cc:67)
==4474==    by 0x1092DC: main (main.cc:20)
==4474== 
已执行错误释放(new[] + free)【场景4:重复释放内存】
==4474== Invalid free() / delete / delete[] / realloc()
==4474==    at 0x484CA8F: operator delete[](void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x10962F: demonstrate_double_free() (main.cc:80)
==4474==    by 0x1092E1: main (main.cc:21)
==4474==  Address 0x4dd62b0 is 0 bytes inside a block of size 10 free'd
==4474==    at 0x484CA8F: operator delete[](void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x10961C: demonstrate_double_free() (main.cc:79)
==4474==    by 0x1092E1: main (main.cc:21)
==4474==  Block was alloc'd at
==4474==    at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109605: demonstrate_double_free() (main.cc:77)
==4474==    by 0x1092E1: main (main.cc:21)
==4474== 
已执行重复释放操作【场景5:内存泄漏】
已分配内存但未释放(内存泄漏)【场景6:释放后使用内存】
==4474== Invalid write of size 4
==4474==    at 0x109759: demonstrate_use_after_free() (main.cc:99)
==4474==    by 0x1092EB: main (main.cc:23)
==4474==  Address 0x4dd6370 is 0 bytes inside a block of size 4 free'd
==4474==    at 0x484B8AF: operator delete(void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109754: demonstrate_use_after_free() (main.cc:98)
==4474==    by 0x1092EB: main (main.cc:23)
==4474==  Block was alloc'd at
==4474==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x109735: demonstrate_use_after_free() (main.cc:95)
==4474==    by 0x1092EB: main (main.cc:23)
==4474== 
已执行释放后使用内存的操作=== 所有场景演示完毕 ===
==4474== 
==4474== HEAP SUMMARY:
==4474==     in use at exit: 40 bytes in 1 blocks
==4474==   total heap usage: 8 allocs, 8 frees, 74,062 bytes allocated
==4474== 
==4474== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4474==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4474==    by 0x1096B0: demonstrate_memory_leak() (main.cc:88)
==4474==    by 0x1092E6: main (main.cc:22)
==4474== 
==4474== LEAK SUMMARY:
==4474==    definitely lost: 40 bytes in 1 blocks
==4474==    indirectly lost: 0 bytes in 0 blocks
==4474==      possibly lost: 0 bytes in 0 blocks
==4474==    still reachable: 0 bytes in 0 blocks
==4474==         suppressed: 0 bytes in 0 blocks
==4474== 
==4474== Use --track-origins=yes to see where uninitialised values come from
==4474== For lists of detected and suppressed errors, rerun with: -s
==4474== ERROR SUMMARY: 45 errors from 9 contexts (suppressed: 0 from 0)

六、解读Valgrind输出

Valgrind的输出包含以下关键部分:

  1. 程序执行信息:程序启动、退出等信息
  2. 错误报告:每个内存错误的详细信息,包括:
    • 错误类型(如Use of uninitialised value)
    • 错误发生的位置(函数调用栈)
    • 内存地址和大小等信息
  3. 泄漏汇总:程序结束时的内存泄漏统计

重点关注标有ERROR SUMMARY的行,例如:ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)表示检测到1个错误。

七、高级技巧

1. 结合GDB调试

  • 使用--vgdb=yes选项,可在Valgrind检测到错误时暂停,通过GDB连接调试:
    valgrind --vgdb=yes --vgdb-error=0 ./myprogram # 启动程序并等待GDB连接

在这里插入图片描述

2. 另一个终端中连接

gdb ./myprogram
(gdb) target remote | vgdb

在这里插入图片描述

使用 valgrind --vgdb=yes --vgdb-error=0 ./myprogram 启动程序后,退出方式取决于程序和调试状态,主要有以下几种方法:

1. 正常退出程序(推荐)

  • 如果程序仍在运行,直接让程序执行到正常结束(如触发 return 0 或 exit()),Valgrind 会随程序一起退出,并输出完整的内存检测报告。
    2. 在 GDB 中终止程序

  • 如果已通过 GDB 连接到 Valgrind(target remote | vgdb),可以在 GDB 终端中执行:

    gdb
    (gdb) quit  # 退出 GDB,会提示是否终止程序
    

    选择 y 确认后,程序和 Valgrind 会被强制终止。

2. 生成内存泄漏抑制文件
对于某些库的已知无害泄漏,可生成抑制文件忽略它们:

  • valgrind --leak-check=full --gen-suppressions=all ./myprogram > suppressions.txt

然后在后续检测中使用:

  • valgrind --leak-check=full --suppressions=suppressions.txt ./myprogram

示例:

示例代码:
```C++
# 步骤1:创建一个包含第三方库无害泄漏的测试程序(示例)
cat > test_leak.cpp << EOF
#include <iostream>
#include <SDL2/SDL.h>  // 假设使用SDL2库,存在已知无害泄漏int main() {// 初始化SDL(假设SDL内部有已知的无害泄漏)SDL_Init(SDL_INIT_VIDEO);// 模拟业务逻辑std::cout << "程序运行中..." << std::endl;// 清理SDLSDL_Quit();return 0;
}
EOF# 步骤2:编译程序(带调试信息)
g++ -g -o test_leak test_leak.cpp $(sdl2-config --cflags --libs)# 步骤3:首次运行并生成抑制文件
valgrind --leak-check=full --gen-suppressions=all ./test_leak > suppressions.txt 2>&1# 步骤4:使用抑制文件重新检测(过滤已知泄漏)
echo -e "\n=== 使用抑制文件检测 ==="
valgrind --leak-check=full --suppressions=suppressions.txt ./test_leak

逐部分拆解 2>&1

  • 2:代表标准错误流(stderr),程序运行中产生的错误信息(如 Valgrind 的警告、编译错误等)默认通过它输出。
  • “>”:是重定向符号,用于改变数据流的流向(比如 “把输出写到文件”)。
    • &1:& 表示 “引用一个文件描述符”,1 对应标准输出流(stdout)。
    • &1 的意思是 “指向 stdout 当前所指向的目标”(而不是字面意义上的 “1” 这个数字)。


文章转载自:

http://Mfp8ZiAH.nchsz.cn
http://xyHMiB8N.nchsz.cn
http://rrSiF7mp.nchsz.cn
http://5K7b3RJK.nchsz.cn
http://46uT4rHz.nchsz.cn
http://s59aqTha.nchsz.cn
http://xG2R1vBO.nchsz.cn
http://aj3vFGHl.nchsz.cn
http://dvbBTsB6.nchsz.cn
http://EpN5HMFq.nchsz.cn
http://wG7jdBCs.nchsz.cn
http://PJsAOZDQ.nchsz.cn
http://Lvw6IgJC.nchsz.cn
http://rlRzgiWW.nchsz.cn
http://AMJ9wmJs.nchsz.cn
http://znVONHIn.nchsz.cn
http://mVJIk6nE.nchsz.cn
http://v62Wooz2.nchsz.cn
http://lsGqtxV2.nchsz.cn
http://Eupqir98.nchsz.cn
http://dQPchoZ8.nchsz.cn
http://E0R9CYA3.nchsz.cn
http://5CKCB8Na.nchsz.cn
http://rS9f9WrE.nchsz.cn
http://dncyAhuG.nchsz.cn
http://AzvaRBtj.nchsz.cn
http://bxFrISUJ.nchsz.cn
http://xjYnKyBb.nchsz.cn
http://3P4fV0As.nchsz.cn
http://U72pZTNm.nchsz.cn
http://www.dtcms.com/a/370248.html

相关文章:

  • echarts实现点击图表添加标记
  • Python带状态生成器完全指南:从基础到高并发系统设计
  • python入门常用知识
  • 【算法】92.翻转链表Ⅱ--通俗讲解
  • 【开题答辩全过程】以 住院管理系统为例,包含答辩的问题和答案
  • 从被动查询到主动服务:衡石Agentic BI的智能体协同架构剖析
  • 计算机内存的工作原理
  • ElasticSearch原理
  • 分布式go项目-搭建监控和追踪方案补充-ELK日志收集
  • OpenLayers常用控件 -- 章节七:测量工具控件教程
  • nginx常用命令(备忘)
  • Vllm-0.10.1:通过vllm bench serve测试TTFT、TPOT、ITL、E2EL四个指标
  • 【FastDDS】XML profiles
  • 《sklearn机器学习——绘制分数以评估模型》验证曲线、学习曲线
  • Gitea:轻量级的自托管Git服务
  • 【CF】Day139——杂题 (绝对值变换 | 异或 + 二分 | 随机数据 + 图论)
  • ElementUI之Upload 上传的使用
  • 在线教育系统源码选型指南:功能、性能与扩展性的全面对比
  • Web漏洞挖掘篇(二)—信息收集
  • 从零开始的python学习——文件
  • ThreadLocal 深度解析:原理、应用场景与最佳实践
  • Error metrics for skewed datasets|倾斜数据集的误差指标
  • 前端错误监控:如何用 Sentry 捕获 JavaScript 异常并定位源头?
  • 9.6 前缀和
  • 快捷:常见ocr学术数据集预处理版本汇总(适配mmocr)
  • Linux系统检测硬盘失败解救方法
  • 内网后渗透攻击--linux系统(横向移动)
  • 【软考架构】第二章 计算机系统基础知识:计算机网络
  • equals 定义不一致导致list contains错误
  • Qt编程之信号与槽