内存泄漏检测实战——Valgrind

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来初步解决一些简单的用户态内存泄漏问题。

相关推荐
VekiSon4 小时前
Linux内核驱动——杂项设备驱动与内核模块编译
linux·c语言·arm开发·嵌入式硬件
2的n次方_4 小时前
CANN Ascend C 编程语言深度解析:异构并行架构、显式存储层级与指令级精细化控制机制
c语言·开发语言·架构
_F_y5 小时前
C语言重点知识总结(含KMP详细讲解)
c语言·开发语言
郝学胜-神的一滴6 小时前
深入解析C/S模型下的TCP通信流程:从握手到挥手的技术之旅
linux·服务器·c语言·网络·网络协议·tcp/ip
island13147 小时前
CANN GE(图引擎)深度解析:计算图优化管线、内存静态规划与异构 Stream 调度机制
c语言·开发语言·神经网络
Once_day8 小时前
C++之《程序员自我修养》读书总结(1)
c语言·开发语言·c++·程序员自我修养
坚果派·白晓明8 小时前
在鸿蒙设备上快速验证由lycium工具快速交叉编译的C/C++三方库
c语言·c++·harmonyos·鸿蒙·编程语言·openharmony·三方库
风指引着方向9 小时前
图编译优化全链路:CANN graph-engine 仓库技术拆解
c语言
C++ 老炮儿的技术栈10 小时前
VS2015 + Qt 实现图形化Hello World(详细步骤)
c语言·开发语言·c++·windows·qt