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

相关推荐
养军博客2 小时前
C语言五天算法速成(可用于备考蓝桥杯)
c语言·算法·蓝桥杯
无限进步_2 小时前
203. 移除链表元素 - 题解与详细分析
c语言·开发语言·数据结构·git·链表·github·visual studio
10岁的博客2 小时前
C语言造轮子大赛
java·c语言·数据结构
你怎么知道我是队长2 小时前
C语言---排序算法1---冒泡排序法
c语言·算法·排序算法
历程里程碑2 小时前
Linux 9:GCC编译全流程详解
linux·运维·服务器·c语言·笔记·编辑器·vim
Once_day3 小时前
C++之《Effective C++》读书总结(1)
c语言·c++·effective c++
爱吃生蚝的于勒3 小时前
【Linux】进程信号的保存(二)
linux·运维·服务器·c语言·数据结构·c++·算法
小程同学>o<3 小时前
嵌入式之C/C++(一)关键字
c语言·开发语言·c++·嵌入式软件面试
郝学胜-神的一滴4 小时前
Linux Socket编程核心:深入解析sockaddr数据结构族
linux·服务器·c语言·网络·数据结构·c++·架构