1、内存泄漏的噩梦:什么是内存泄漏、内存泄漏的危害、一个真实的内存泄漏事故
1.1 什么是内存泄漏?
说白了,内存泄漏就是「借了不还」。
你找操作系统借了一块内存,用完之后忘了还回去。一次两次没事,但日积月累,系统手里可用的内存就越来越少。我见过不少新手,觉得内存管理是小事,结果项目跑到一半,程序突然崩了——嗯,八成就是内存泄漏在作祟。
在C语言里,我们用 malloc()、calloc()、realloc() 这些函数向堆上申请内存。用完以后,必须用 free() 还回去。如果你丢了指向这块内存的指针,或者忘了调用 free(),那这块内存就永远收不回来了。
看个最简单的例子:
#include <stdlib.h>
void leak_example() {
int *p = (int *)malloc(100 * sizeof(int));
// 用完了,但忘了 free(p);
// 函数返回后,指针 p 没了,这块内存再也找不回来了
}
int main() {
for (int i = 0; i < 10000; i++) {
leak_example(); // 每次调用泄漏 400 字节
}
// 程序退出时,泄漏了约 4MB 内存
return 0;
}
你想想看,每次泄漏400字节,循环一万次就是4MB。如果这是个服务器程序,7x24小时跑着,几天下来内存就被吃光了。
1.2 内存泄漏的危害
我在项目中遇到过最头疼的问题,就是内存泄漏导致的「慢性死亡」。它不是立刻崩溃,而是慢慢蚕食系统资源。具体来说,危害有这几方面:
- 程序性能下降: 内存越来越少,系统不得不频繁使用交换分区(swap),磁盘I/O暴增,程序越来越慢。
- 系统资源耗尽: 泄漏严重时,操作系统可能杀掉你的进程,或者整个系统卡死。
- 难以定位: 内存泄漏的bug往往在运行几小时甚至几天后才暴露,复现困难,排查起来非常痛苦。
- 安全风险: 某些情况下,攻击者可以利用内存泄漏获取敏感信息,或者实施拒绝服务攻击。
| 泄漏规模 | 表现 | 发现难度 |
|---|---|---|
| 小(每次几字节) | 长期运行后内存缓慢增长 | 极难 |
| 中(每次几百字节) | 几小时后程序变慢 | 中等 |
| 大(每次几MB) | 几分钟内系统卡死 | 较易 |
1.3 一个真实的内存泄漏事故
讲个我亲身经历的事吧。
几年前,我参与过一个嵌入式网关项目。设备需要7x24小时不间断运行,处理网络数据包。测试阶段一切正常,但部署到客户现场后,大概每运行3天左右,设备就会死机,必须重启。
一开始我们怀疑是硬件问题,换了内存条、换了电源,问题依旧。后来我抓了个crash dump,一看——好家伙,内存占用从启动时的30MB,一路涨到接近200MB,直到系统OOM(Out Of Memory)杀掉进程。
排查过程很痛苦。代码量十几万行,一个个函数看过去,眼睛都快瞎了。最后我用 valgrind 跑了一遍,定位到一个网络数据包处理函数:
void process_packet(char *raw_data, int len) {
char *buffer = (char *)malloc(len + 128);
// 处理数据...
// 正常情况下,处理完后应该 free(buffer)
// 但有一个异常分支,直接 return 了,忘了 free
if (some_error_condition) {
return; // 这里泄漏了!
}
// 正常处理...
free(buffer);
}
你看,就少了一个 free()。每次处理异常数据包时泄漏一块内存。设备每天处理几十万个包,三天下来,内存就被吃光了。
malloc 的时候,会立刻把对应的 free 写出来,再往中间填业务逻辑。这叫「成对编程」,能有效减少遗漏。
修复很简单,加个 free(buffer) 就完事了。但排查花了我整整一周。你想想看,一个团队一周的人力成本,就为了补一个 free。值不值?当然不值。所以内存泄漏这件事,预防比事后排查重要得多。
1.4 本章知识体系
下面这张图,帮你理清内存泄漏的核心脉络:
嗯,这一章就讲到这里。内存泄漏不是什么高深的概念,但它的破坏力不容小觑。记住一句话:你借的每一块内存,都要亲手还回去。
free 漏掉,导致整个项目延期三天。从那以后,我写代码前都会先画好内存的生命周期图。你也试试?