1. 内存泄漏基础:什么是内存泄漏、内存泄漏的危害、常见的内存泄漏场景
1.1 什么是内存泄漏?
说白了,内存泄漏就是「你借了东西不还」。
在C/C++里,我们用 malloc、new 向系统申请了一块内存,用完之后却没有用 free、delete 还回去。这块内存就「丢」了——程序再也找不到它,系统也没法回收它。
我习惯这么理解:内存泄漏 = 申请 + 丢失 + 永不释放。
举个例子:
void leak_example() {
int *p = (int*)malloc(100 * sizeof(int));
// 用了一下 p
// 但忘了 free(p)
return; // p 丢了!100个int的内存永远回不来了
}
你看,函数返回后,指针 p 没了,但那块内存还在。程序再也找不到它了。这就是泄漏。
1.2 内存泄漏的危害
你可能觉得:「不就漏了一点内存吗?能有多大问题?」
嗯,我刚开始做嵌入式开发时也这么想。直到有一次,我们的设备在客户现场跑了三天,突然挂了。查了整整一周,发现是一个循环里每次漏了 64 字节,三天下来累积了几百兆,直接把堆耗尽了。
内存泄漏的危害,我总结为四点:
| 危害 | 说明 | 真实场景 |
|---|---|---|
| 程序崩溃 | 堆耗尽后,malloc/new 返回 NULL,程序直接挂 | 服务器运行几天后突然宕机 |
| 性能下降 | 内存碎片增多,分配速度变慢 | 系统越来越卡,响应延迟飙升 |
| 系统不稳定 | 占用系统内存,影响其他进程 | 嵌入式设备死机、重启 |
| 难以排查 | 泄漏往往在特定路径下才触发 | 测试时正常,上线后出问题 |
1.3 常见的内存泄漏场景
我在项目中遇到过各种各样的泄漏。说白了,大部分泄漏都是「人祸」——要么是忘了释放,要么是释放错了地方。下面这几种场景,你十有八九也碰到过。
场景一:忘记释放
最基础的,也是最常见的。申请了内存,但释放代码被跳过了。
void process_data(int flag) {
char *buf = (char*)malloc(1024);
if (flag == 0) {
return; // 忘了 free(buf)!
}
// 正常处理...
free(buf);
}
你看,flag == 0 时直接 return 了,free 根本没机会执行。这种错误,我建议你在写代码时就养成习惯:申请内存后,立刻写对应的释放代码。
场景二:释放不完全
这个坑我踩过好几次。比如你有一个结构体,里面嵌套了指针:
typedef struct {
char *name;
int age;
} Person;
Person *create_person() {
Person *p = (Person*)malloc(sizeof(Person));
p->name = (char*)malloc(64); // 内部又申请了内存
return p;
}
void destroy_person(Person *p) {
free(p); // 只释放了 Person 本身
// 但 p->name 没释放!泄漏了!
}
你想想看,free(p) 只释放了外层结构体,内部的 name 指针指向的那块内存还在。这就是「释放不完全」。
场景三:异常路径泄漏
这是最隐蔽的一种。程序正常跑没问题,但一旦遇到错误、异常、提前返回,泄漏就出现了。
int read_file(const char *path) {
FILE *fp = fopen(path, "r");
char *buf = (char*)malloc(4096);
if (fp == NULL) {
return -1; // 泄漏了 buf!
}
if (fread(buf, 1, 4096, fp) < 0) {
fclose(fp);
return -2; // 又泄漏了 buf!
}
// 正常处理...
free(buf);
fclose(fp);
return 0;
}
我曾经在一个网络协议栈里排查过一个类似的泄漏。正常收发数据没问题,但一旦网络断开、重连,每次都会漏掉几百字节。跑了两个月,内存占用从 2MB 涨到了 800MB。
场景四:容器中的泄漏
用 STL 容器时,如果存的是指针,很容易出问题:
std::vector<int*> vec;
for (int i = 0; i < 100; i++) {
vec.push_back(new int(i));
}
// 用完了...
vec.clear(); // 只清空了指针,没释放指向的内存!
// 100个int全泄漏了
你想想看,clear() 只是把容器里的指针清掉了,但 new 出来的那些 int 还在堆上。正确的做法是遍历释放,或者直接用 unique_ptr。
1.4 知识体系总览
下面这张图,是我梳理的内存泄漏知识体系。你可以把它当作整个课程的地图:
1.5 小结
这一章我们聊了内存泄漏的「是什么、为什么、怎么来」。说白了,内存泄漏就是「借了不还」,它像慢性毒药一样慢慢侵蚀你的程序。
我个人觉得,理解泄漏的本质比学会排查工具更重要。因为只有你知道「泄漏是怎么发生的」,才能在写代码时就把它扼杀在摇篮里。
嗯,下一章我们开始动手——我会带你用 Valgrind 抓出第一个泄漏。不过在那之前,建议你把今天讲的几种场景在自己的代码里找一找,说不定就有惊喜(或者惊吓)。