A04 —— 内存泄漏:malloc 不 free 是怎么导致泄漏的
金句:程序结束后,系统会帮你回收——但在运行期间,泄漏的内存就是真的泄漏了。
什么是内存泄漏
内存泄漏(Memory Leak)是指程序动态分配内存后,没有释放,导致这块内存永远无法被 reuse。
内存泄漏的典型例子:
void leak() {
char *buf = malloc(1024);
if (some_condition) {
return; // 提前返回,没有 free
}
free(buf); // 正常路径释放
}
void real_leak() {
while (1) {
char *buf = malloc(1024);
// 忘记 free(buf);
sleep(1);
}
}内存泄漏的分类:
1. 真正的泄漏(True Leak):
- 指针丢失,内存无法访问
- 内存无法被任何代码释放
- 必须重启程序才能回收
2. 逻辑泄漏(Logical Leak):
- 指针还在,但不再使用
- 内存仍然可达,但已经是垃圾
- 修复代码可以解决
3. 循环引用(C++ 中):
- 对象 A 持有对象 B 的引用,B 也持有 A 的引用
- 两者都无法被 GC 回收
- 用 weak_ptr 打破循环泄漏是如何发生的
常见的内存泄漏场景:
1. 提前 return:
if (cond) return; // buf 没有 free
...
free(buf);
2. 错误处理遗漏:
if (!buf) return ERROR;
buf = malloc(...);
if (err) return ERROR; // buf 没有 free
...
free(buf);
3. 循环中累积:
while (process()) {
char *tmp = malloc(1024);
do_something(tmp);
// 忘记 free(tmp)
}
4. 指针赋值覆盖:
char *buf = malloc(1024);
buf = malloc(2048); // 第一个 1024 泄漏
free(buf); // 只 free 第二个进程结束后内存会回收吗
进程退出后,内存会被系统回收:
- 进程退出时,所有打开的文件描述符关闭
- 所有内存映射(mmap/vma)被释放
- 所有物理页归还 buddy system
- 进程持有的内存全部回收
所以:
- 泄漏的内存,进程退出后会被回收
- 但如果进程是长期运行的(服务器),泄漏会累积,直到 OOM
泄漏的影响:
- 短命程序(脚本):影响小,退出就回收
- 长命程序(服务器/daemon):累积,最终 OOM Killer 杀掉检测内存泄漏的工具
1. valgrind --leak-check=full:
valgrind --leak-check=full ./program
输出:
==12345== 120 bytes in 1 blocks are definitely lost
==12345== at 0x...: malloc (vg_malloc.c:...)
==12345== by 0x...: leak() (leak.c:10)
leak summary:
definitely lost: 120 bytes(真正泄漏)
indirectly lost: 0 bytes(间接泄漏)
still reachable: 1024 bytes(全局变量持有,未泄漏)
2. AddressSanitizer:
gcc -fsanitize=address -g program.c
./program
ASan 会在运行时检测:
- 泄漏(leak)
- 访问已释放内存(use-after-free)
- 越界访问(buffer overflow)
3. /proc/PID/status 监控:
$ ps -o pid,rss,vsz,comm | grep myprogram
PID RSS VSZ COMM
12345 54321 123456 myprogram
如果 RSS 持续增长 = 有泄漏
4. mtrace(glibc 内置):
#include <mcheck.h>
mtrace();
运行:
MALLOC_TRACE=/tmp/trace.log ./program
mtrace ./program /tmp/trace.log
→ 报告所有 malloc/free 调用防止内存泄漏的方法
C++ 智能指针:
void leak() {
auto buf = std::make_unique<char[]>(1024);
// ...
} // 函数结束,buf 自动释放(delete[])
RAII 模式:
class Buffer {
char *data;
public:
Buffer(size_t size) { data = new char[size]; }
~Buffer() { delete[] data; } // 析构自动释放
};
void func() {
Buffer buf(1024);
// ...
} // buf 超出作用域,析构函数自动调用
C 语言 cleanup 属性:
void func() {
char *buf __attribute__((cleanup(cleanup_free))) = malloc(1024);
// ...
}
static void cleanup_free(char **p) {
if (*p) free(*p);
}
谁分配谁释放原则:
- 每个 malloc 必须配对一次 free
- 建议封装成函数或用 RAII总结
- 内存泄漏:malloc 后未 free,指针丢失,无法回收
- 真正泄漏:指针丢失,无法访问;逻辑泄漏:指针还在但不用
- 长命程序:短命程序退出自动回收,长命程序累积导致 OOM
- 检测:valgrind(最全)/ ASan(快)/ /proc/status(监控)/ mtrace(glibc)
- 防止:智能指针(C++)/ RAII 析构函数 / cleanup 属性 / 谁分配谁释放
下篇预告(A05):什么是 use-after-free?为什么释放后再访问会导致严重问题,以及怎么检测。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论