A04 — Memory Leaks: How malloc Without free Causes Leaks
Key Insight: After the program ends, the system reclaims the memory. But during execution, leaked memory is truly leaked.
What is a Memory Leak
A memory leak occurs when a program dynamically allocates memory and fails to free it, making that memory permanently unavailable for 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 打破循环How Leaks Happen
常见的内存泄漏场景:
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 第二个Is Memory Reclaimed After Process Exit?
进程退出后,内存会被系统回收:
- 进程退出时,所有打开的文件描述符关闭
- 所有内存映射(mmap/vma)被释放
- 所有物理页归还 buddy system
- 进程持有的内存全部回收
所以:
- 泄漏的内存,进程退出后会被回收
- 但如果进程是长期运行的(服务器),泄漏会累积,直到 OOM
泄漏的影响:
- 短命程序(脚本):影响小,退出就回收
- 长命程序(服务器/daemon):累积,最终 OOM Killer 杀掉Tools for Detecting Memory Leaks
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 调用Methods to Prevent Memory Leaks
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
- 建议封装成函数或用 RAIISummary
- Memory Leak: malloc without free; pointer lost; cannot be reclaimed
- True Leak: Pointer lost, cannot be accessed. Logical Leak: pointer exists but is no longer used
- Long-Lived Programs: Short-lived programs auto-reclaim on exit; long-lived programs accumulate, leading to OOM
- Detection: valgrind (most comprehensive) / ASan (fast) / /proc/status (monitoring) / mtrace (glibc)
- Prevention: Smart pointers (C++) / RAII destructors / cleanup attributes / who allocates frees
Next (A05): What is use-after-free? Why accessing freed memory causes serious problems, and how to detect it.
Comments