4. valgrind内存检测.md 13 KB


title: valgrind内存检测 type: raw created: 2026-08-31

tags: [debugging, valgrind, memory, linux, c-cpp]

valgrind内存检测

1. valgrind是什么

valgrind是一套强大的内存调试、性能分析和检测工具集。它通过在程序运行时拦截系统调用和内存操作,检测各种内存相关问题:

  • 内存泄漏检测
  • 未初始化内存使用
  • 非法内存访问
  • 内存越界读写
  • 双重释放
  • 不匹配的malloc/free

1.1 valgrind包含的工具

工具 说明
memcheck 内存错误检测(默认工具)
cachegrind 缓存性能分析
callgrind 函数调用分析
helgrind 多线程竞争检测
drd 多线程数据竞争检测
massif 堆内存分析
lackey 指令计数

2. 内存泄漏类型

2.1 直接泄漏(Definitely Lost)

程序中malloc/new后未对应free/delete,且无任何指针指向该内存:

void direct_leak() {
    char *p = malloc(100);  // 直接泄漏:p未释放
    // 缺少 free(p);
}

后果:程序运行期间内存持续占用,进程退出时由OS回收。

2.2 间接泄漏(Indirectly Lost)

指针丢失导致整块内存无法访问:

void indirect_leak() {
    int *arr = malloc(10 * sizeof(int));  // arr是直接泄漏
    int *ptr = arr + 5;  // ptr指向arr中间
    free(arr);  // 只释放了arr的起始地址
    // ptr指向的内存泄漏
}

2.3 可修复泄漏(Reachable / Still Reachable)

程序退出前未释放但指针仍可达的内存:

static char *global_ptr;

void reachable_leak() {
    global_ptr = malloc(100);
    // 函数结束时global_ptr仍可达
    // 未释放但不影响程序运行
}

2.4 可能的泄漏(Possibly Lost)

指针指向已释放内存区域中间位置:

void possible_leak() {
    int *arr = malloc(10 * sizeof(int));
    int *ptr = arr + 5;
    free(arr);
    // ptr指向已释放内存,valgrind报告可能泄漏
}

3. valgrind常用命令

3.1 基本使用

# 基本内存检测
valgrind --leak-check=full ./program

# 显示详细信息
valgrind -v --leak-check=full ./program

# 生成日志文件
valgrind --log-file=valgrind.log --leak-check=full ./program

3.2 常用选项

# 追踪未初始化变量来源
valgrind --track-origins=yes --leak-check=full ./program

# 显示可到达的泄漏(默认隐藏)
valgrind --show-reachable=yes --leak-check=full ./program

# 显示所有泄漏(包括可修复的)
valgrind --show-possibly-lost=yes --leak-check=full ./program

# 检测双重释放
valgrind --check-stackrefs=yes ./program

# 指定错误上限
valgrind --errors-for-leak-kinds=definite --leak-check=full ./program

3.3 完整选项列表

选项 说明
--leak-check=full 完整泄漏检查
--leak-check=summary 仅显示摘要
--leak-check=no 不检查泄漏
--show-reachable=yes 显示可到达的泄漏
--show-possibly-lost=yes 显示可能的泄漏
--track-origins=yes 追踪未初始化值来源
--error-exitcode=1 检测到错误时返回非零值
--log-file=FILE 输出到日志文件
--suppressions=FILE 使用抑制规则文件
--trace-children=yes 跟踪子进程
--num-callers=N 调用栈深度

4. 输出结果解读

4.1 典型输出示例

==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2023, and GNU GPL'd, by Julian Seward.
==12345== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==12345== Command: ./program
==12345==
==12345== Invalid read of size 4
==12345==    at 0x10917A: main (test.c:10)
==12345==  Address 0x4a2a040 is 0 bytes after a block of size 40 alloc'd
==12345==    at 0x4848899: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x10916B: main (test.c:8)
==12345==
==12345==
==12345== HEAP SUMMARY:
==12345==     in use at exit: 40 bytes in 1 blocks
==12345==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==12345==
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4848899: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x10916B: main (test.c:8)
==12345==
==12345== LEAK SUMMARY:
==12345==    definitely lost: 40 bytes in 1 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 0 bytes in 0 blocks
==12345==    still reachable: 0 bytes in 0 blocks
==12345==         suppressed: 0 bytes in 0 blocks
==12345==
==12345== For lists of detected and suppressed errors, rerun with: -s
==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

4.2 输出字段说明

字段 说明
Invalid read/write 非法内存读写
definitely lost 确定泄漏(无指针指向)
indirectly lost 间接泄漏(父指针丢失)
possibly lost 可能泄漏(指针指向块中间)
still reachable 可达但未释放
suppressed 被抑制的错误

4.3 常见错误信息

# 非法读取
Invalid read of size N
# 原因:访问已释放或未分配的内存

# 非法写入
Invalid write of size N
# 原因:写入已释放内存或越界

# 使用未初始化值
Conditional jump or move depends on uninitialised value(s)
# 原因:使用未初始化的变量

# 系统调用参数错误
Syscall param ... points to uninitialised byte(s)
# 原因:传递未初始化数据给系统调用

5. 常见内存错误类型

5.1 内存泄漏

void memory_leak() {
    int *p = malloc(100);
    // 缺少 free(p);
}

5.2 越界访问

void out_of_bounds() {
    int arr[10];
    arr[10] = 1;  // 越界写入
}

5.3 使用已释放内存

void use_after_free() {
    int *p = malloc(100);
    free(p);
    *p = 10;  // 使用已释放内存
}

5.4 双重释放

void double_free() {
    int *p = malloc(100);
    free(p);
    free(p);  // 双重释放
}

5.5 未初始化变量

void uninitialized() {
    int x;
    if (x > 0) {  // x未初始化
        printf("positive\n");
    }
}

5.6 不匹配的分配方式

void mismatch() {
    int *p = new int;
    free(p);  // 应该用 delete
}

6. 嵌入式环境中的替代方案

6.1 轻量级检测工具

// 自定义内存跟踪器
#define TRACK_MALLOC(size) tracked_malloc(size, __FILE__, __LINE__)
#define TRACK_FREE(ptr) tracked_free(ptr)

static void *tracked_malloc(size_t size, const char *file, int line) {
    void *p = malloc(size);
    printf("MALLOC: %p at %s:%d\n", p, file, line);
    return p;
}

static void tracked_free(void *p) {
    printf("FREE: %p\n", p);
    free(p);
}

6.2 内存池

// 固定大小内存池,避免频繁malloc/free
#define POOL_SIZE 1024
static char pool[POOL_SIZE];
static size_t pool_used = 0;

void *pool_alloc(size_t size) {
    if (pool_used + size > POOL_SIZE) return NULL;
    void *p = &pool[pool_used];
    pool_used += size;
    return p;
}

6.3 AddressSanitizer(编译时检测)

# 编译时启用
gcc -fsanitize=address -g -o program program.c

# 运行时自动检测
./program

6.4 mtrace(GNU工具)

# 设置环境变量
export MTRACE=/tmp/mtrace.log
./program
# 分析日志
mtrace ./program /tmp/mtrace.log

7. 代码示例

7.1 有内存泄漏的程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *name;
    int age;
} Person;

Person *create_person(const char *name, int age) {
    Person *p = malloc(sizeof(Person));
    if (!p) return NULL;

    p->name = malloc(strlen(name) + 1);
    if (!p->name) {
        free(p);
        return NULL;
    }

    strcpy(p->name, name);
    p->age = age;
    return p;
}

void process_data() {
    int *buffer = malloc(100 * sizeof(int));
    for (int i = 0; i < 100; i++) {
        buffer[i] = i;
    }
    // 缺少 free(buffer);
}

void leak_example() {
    Person *alice = create_person("Alice", 25);
    Person *bob = create_person("Bob", 30);

    printf("Alice: %s, %d\n", alice->name, alice->age);
    printf("Bob: %s, %d\n", bob->name, bob->age);

    // 缺少释放
    // free(alice->name);
    // free(alice);
    // free(bob->name);
    // free(bob);

    process_data();
}

int main() {
    leak_example();
    return 0;
}

7.2 valgrind检测结果

$ gcc -g -o leak_demo leak_demo.c
$ valgrind --leak-check=full --track-origins=yes ./leak_demo
==12345== Memcheck, a memory error detector
==12345== Command: ./leak_demo
==12345==
Alice: Alice, 25
Bob: Bob, 30
==12345==
==12345== HEAP SUMMARY:
==12345==     in use at exit: 160 bytes in 4 blocks
==12345==   total heap usage: 6 allocs, 2 frees, 520 bytes allocated
==12345==
==12345== 32 bytes in 2 blocks are definitely lost in loss record 1 of 2
==12345==    at 0x4848899: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x109205: create_person (leak_demo.c:12)
==12345==    by 0x1092A8: leak_example (leak_demo.c:32)
==12345==    by 0x1092F4: main (leak_demo.c:41)
==12345==
==12345== 128 bytes in 2 blocks are definitely lost in loss record 2 of 2
==12345==    at 0x4848899: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x109237: create_person (leak_demo.c:16)
==12345==    by 0x1092A8: leak_example (leak_demo.c:32)
==12345==    by 0x1092F4: main (leak_demo.c:41)
==12345==
==12345== LEAK SUMMARY:
==12345==    definitely lost: 160 bytes in 4 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 0 bytes in 0 blocks
==12345==    still reachable: 0 bytes in 0 blocks
==12345==         suppressed: 0 bytes in 0 blocks
==12345==
==12345== For lists of detected and suppressed errors, rerun with: -s
==12345== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

7.3 修复后的程序

void leak_example_fixed() {
    Person *alice = create_person("Alice", 25);
    Person *bob = create_person("Bob", 30);

    printf("Alice: %s, %d\n", alice->name, alice->age);
    printf("Bob: %s, %d\n", bob->name, bob->age);

    // 正确释放
    free(alice->name);
    free(alice);
    free(bob->name);
    free(bob);

    process_data_fixed();
}

void process_data_fixed() {
    int *buffer = malloc(100 * sizeof(int));
    for (int i = 0; i < 100; i++) {
        buffer[i] = i;
    }
    free(buffer);  // 释放内存
}

8. 注意事项

  1. 性能影响:valgrind会使程序运行速度降低5-50倍,内存使用增加2-5倍
  2. 无法检测栈内存:valgrind主要检测堆内存,栈变量越界可能无法发现
  3. 抑制文件:对于已知问题可创建抑制文件忽略特定警告
  4. 权限要求:通常不需要root权限,但调试其他用户进程需要
  5. 动态库:需要库也带调试信息才能准确报告行号
  6. 多线程:使用--fair-sched=yes获得更准确的多线程结果

8.1 抑制文件示例

// suppressions.supp
{
   glibc_still_reachable
   Memcheck:Leak
   match-leak-kinds: reachable
   ...
   fun:_dl_*
   ...
}
valgrind --suppressions=suppressions.supp ./program

9. 面试要点

高频问题

  1. valgrind如何检测内存泄漏?

    • 通过拦截malloc/free调用,记录分配和释放的对应关系
  2. definitely lost和possibly lost的区别?

    • definitely lost:无指针指向;possibly lost:指针指向块中间
  3. valgrind的性能影响?

    • 运行速度降低5-50倍,内存增加2-5倍
  4. 如何在生产环境使用?

    • 开发/测试阶段使用,生产环境使用AddressSanitizer或自定义检测
  5. valgrind能检测哪些错误?

    • 内存泄漏、越界访问、使用已释放内存、双重释放、未初始化变量
  6. valgrind和ASan的区别?

    • valgrind:运行时检测,无需重新编译;ASan:编译时插入检测代码

常见命令速查

# 基本检测
valgrind --leak-check=full ./program

# 追踪未初始化值
valgrind --track-origins=yes ./program

# 生成日志
valgrind --log-file=valgrind.log ./program

# 抑制特定错误
valgrind --suppressions=supp.supp ./program

本文件用于知识归档,内容可直接用于学习与复习。