Valgrind 是一个开源的动态分析工具框架,最著名的组件是 Memcheck,用于检测内存错误。 主要功能(通过 Memcheck):
- 检测内存泄漏(malloc 后未 free)
- 使用已释放的内存(use-after-free)
- 数组/缓冲区越界访问(堆、栈、全局区)
- 使用未初始化的内存
- 内存管理函数使用不当(如 free 非法指针) 特点:
- 不修改源码:直接运行编译后的二进制文件。
- 动态插桩(Dynamic Binary Instrumentation):在运行时将目标程序"翻译"成中间表示并插入检查代码。
- 跨平台:支持 Linux、macOS(部分)、BSD 等,但不支持 Windows。
- 性能开销大:程序运行速度可能变慢 10~50 倍。
- 内存占用高:需要额外内存来记录元数据。 使用示例: valgrind --tool=memcheck --leak-check=full ./your_program
本文是内存泄漏板块的第一篇文章,我们首先简单介绍以下使用Valgrind来解决一下用户态内存泄漏问题
一、准备工作
1. 安装Valgrind
根据当前环境,安装对应的内存检测工具Valgrind
1.1 查看当前ubuntu版本
🔧方法 1:使用 lsb_release 命令(推荐)
lsb_release -a

如果你只想查看简要信息,可以使用:
lsb_release -d

🔧方法 2:查看 /etc/os-release 文件

你也可以提取特定字段,比如:
grep "PRETTY_NAME" /etc/os-release | cut -d '"' -f2

🔧方法 3:使用 hostnamectl 命令

方法 4:使用 uname 查看内核版本(注意:这不是 Ubuntu 发行版版本)
uname -r

推荐方法1和方法2获取的结果最准确
如有需要,也可以写成脚本一键获取版本:

1.2 安装 Valgrind
Ubuntu 22.04 默认软件源中已包含 valgrind,直接通过 apt 安装:
sudo apt update
sudo apt install valgrind -y


验证是否安装成功:
valgrind --version
# 输出类似:valgrind-3.18.1

⚠️ 注意:Ubuntu 22.04 自带的 Valgrind 版本通常为 3.18 左右,对现代 C/C++ 程序支持良好。
2. 准备一个测试程序(含内存泄漏)
//memory_leak_check.c
int main(int argc, char * argv[]) {
int *ptr = (int *)malloc(10 * sizeof(int)); // 分配 10 个整数
ptr[10] = 42; // 错误:越界访问!数组大小为 10,索引 10 是第 11 个元素
// 忘记释放内存 ------ 内存泄漏
// free(ptr);
return 0;
}
3. 编译程序(必须加-g)
为了在 Valgrind 报告中看到源码行号,必须用 -g 编译:
gcc -g -o memory_leak_check memory_leak_check.c

如果你不加 -g,Valgrind 虽然仍能检测泄漏,但无法告诉你具体是哪一行代码导致的。
4. 使用 Valgrind 检测内存泄漏
运行以下命令:
valgrind --tool=memcheck \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
./$(TARGET)
4.1 命令组合
为了方便测试和以后拓展,我们实现以下几个命令:
| 命令 | 作用 |
|---|---|
| make 或 make all | 编译程序,等价于 gcc -g -o memory_leak_check memory_leak_check.c |
| make run | 编译后直接运行 ./memory_leak_check |
| make leakcheck | 用 valgrind 检查内存泄漏(非常有用!) |
| make clean | 删除可执行文件、目标文件等 |
| make help | 查看帮助说明 |
🛠️ 编写Makefile文件
Makefile 关键点解释
| 内容 | 说明 |
|---|---|
| CC = gcc | 设置编译器为 gcc |
| CFLAGS = -Wall -Wextra -g | 启用所有警告 + 调试信息 |
| (TARGET): (SRC) | 规则:当源文件更新时重新编译 |
| .PHONY | 防止文件名冲突(比如你有个叫 clean 的文件) |
| @echo | 开头的 @ 表示不显示该命令本身,只显示输出内容 |
#Makefile - 构建并管理memory_leak_check 程序
#编译器
CC = gcc
#编译选项:-g 添加调试符号,便于调试和valgrind 定位问题
CFLAGS = -Wall -Wextra -g
#目标可执行文件名
TARGET = memory_leak_check
#源文件
SRC = memory_leak_check.c
#默认目标:构建程序
all: $(TARGET)
#主目标规则:从源文生成可执行文件
$(TARGET):$(SRC)
$(CC) $(CFLAGS) -o $(TARGET) $(SRC)
#编译并静默运行(可选)
run: $(TARGET)
./$(TARGET)
#使用varlgrind检查内存泄漏(推荐开发时使用)
leakcheck: $(TARGET)
valgrind --tool=memcheck \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
./$(TARGET)
#清理编译产物
clean:
rm -f $(TARGET) $(SRC:.c=.o) *.o a.out
echo "Clean complete: removed $(TARGET) and object files"
#打印帮助信息
help:
@echo "使用方法:"
@echo "make - 编译程序"
@echo "make run - 编译并运行"
@echo "make leakcheck - 编译并用 valgrind 检测内存泄漏"
@echo "make clean - 删除生成的文件"
@echo "make help - 显示此帮助"
#声明这些是"伪目标"(不是真实文件名)
.PHONY: all run leakcheck clean help
🛠️ 编译并使用valgrind进行内存泄漏检测
-
make 编译

-
make run 运行

-
make leakcheck 利用valgrind进行内存泄漏检测


程序在运行过程中发生了 2 个独立的内存错误("2 errors from 2 contexts"),都是由于 越界写入(out-of-bounds write)堆内存 导致的。
虽然只显示了一个详细错误,但总共有 两个类似的非法写操作。
我们先改一个地方重新编译运行,看看valgrind如果处理
并且可以看出在代码第8行,明显越界访问了一段非法内存,我们修改代码
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char * argv[]) {
int *ptr = (int *)malloc(10 * sizeof(int)); // 分配 10 个整数
ptr[9] = 42; // 错误:越界访问!数组大小为 10,索引 10 是第 11 个元素
// 忘记释放内存 ------ 内存泄漏
//free(ptr);
return 0;
}
- make clean 清除缓存

之后重新执行make,make run, make leakcheck

可以发现,仍然存在一个堆内存泄漏,我们在使用完malloc之后没有进行堆内存释放,从而发生了内存泄漏
我们改掉剩余的一个错误重新make,make run, make leakcheck
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char * argv[]) {
int *ptr = (int *)malloc(10 * sizeof(int)); // 分配 10 个整数
ptr[9] = 42; // 错误:越界访问!数组大小为 10,索引 10 是第 11 个元素
// 忘记释放内存 ------ 内存泄漏
free(ptr);
return 0;
}

此时显示,所有的内存泄漏问题已经全部解决。
至此,我们学会了使用Valgrind来初步解决一些简单的用户态内存泄漏问题。