Valgrind检测内存泄漏入门指南


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

目录


Valgrind使用指南

一、Valgrind简介

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

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

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

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

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

bash 复制代码
sudo apt-get install valgrind

2. Linux系统(CentOS/RHEL)

bash 复制代码
sudo yum install valgrind

3. macOS系统# 使用Homebrew

bash 复制代码
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

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

五、实战示例

cpp 复制代码
#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[]释放,而非free
    free(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

输出:

bash 复制代码
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. 另一个终端中连接

bash 复制代码
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 终端中执行:

    bash 复制代码
    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

示例:

bash 复制代码
示例代码:
```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;
    
    // 清理SDL
    SDL_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" 这个数字)。

相关推荐
864记忆18 小时前
ubuntu18.04安装五笔字型的方法
linux
愚润求学18 小时前
【Linux】TCP原理
linux·tcp/ip
1白天的黑夜118 小时前
Linux(4)|入门的开始:Linux基本指令(4)
linux·运维·服务器·centos
鹏大师运维19 小时前
信创环境下的远程桌面新选择:RustDesk自建服务全攻略
linux·开源·操作系统·远程桌面·麒麟·rustdesk·统信uos
___波子 Pro Max.19 小时前
Linux与STM32实时性与系统资源解析
linux·stm32
励志不掉头发的内向程序员19 小时前
【Linux系列】让 Vim “跑”起来:实现一个会动的进度条
linux·运维·服务器·开发语言·学习
Ronin30519 小时前
【Linux网络】Socket编程:UDP网络编程实现DictServer
linux·服务器·网络·udp
SundayBear20 小时前
基于MCU的文件系统
linux·服务器·单片机
爱隐身的官人1 天前
Linux配置Java/JDK(解决Kali启动ysoserial.jar JRMPListener报错)暨 Kali安装JAVA8和切换JDK版本的详细过程
java·linux·kali
Algebraaaaa1 天前
Linux 基本命令超详细解释第三期 grep | wc | 管道符‘|’ | echo | tail | 重定向符
linux