1. 内存泄漏基础:什么是内存泄漏、内存泄漏的危害、常见的内存泄漏场景
1.1 到底什么是内存泄漏?
说白了,内存泄漏就是「借了不还」。
你向操作系统申请了一块内存,用完之后忘了释放。这块内存就永远「失踪」了——别人用不了,系统也收不回来。我刚开始学C语言时,觉得这没什么大不了的。直到有一次,我负责的一个嵌入式设备跑了三天后突然死机……嗯,从那以后我再也不敢小看内存泄漏了。
用专业点的话说:内存泄漏是指动态分配的内存(malloc/calloc/realloc)在生命周期结束后,没有通过free()归还给系统。
核心公式:
malloc() 次数 > free() 次数 = 内存泄漏
1.2 内存泄漏的危害有多大?
你可能觉得「漏一点内存怕什么?」——我告诉你,真怕。
我在项目中遇到过这样一个案例:一个数据采集模块,每次采集都malloc 1KB内存,但忘记free。刚开始跑得好好的,但48小时后,系统可用内存从64MB降到了2MB。最后整个系统卡死,连看门狗都救不回来。
具体来说,内存泄漏的危害有这几条:
- 程序越来越慢——内存碎片化,malloc耗时增加
- 系统资源耗尽——最终导致malloc返回NULL,程序崩溃
- 隐藏的定时炸弹——平时不触发,特定场景才暴露
- 嵌入式设备尤其危险——没有swap,内存用完就真没了
⚠️ 特别注意:在嵌入式裸机或RTOS环境下,内存泄漏往往是致命的。我曾经见过一个产品因为1天泄漏200字节,运行3个月后彻底罢工——返厂维修成本高得吓人。
1.3 常见的内存泄漏场景
我总结了工作中最常见的几种泄漏场景,你看看自己中过几枪:
场景一:忘记free
最基础的错误,但也是最容易犯的。
void func() {
int *p = (int*)malloc(100 * sizeof(int));
// 用p做了一些操作...
// 忘记free(p);
}
每次调用func(),就泄漏400字节(假设int占4字节)。
场景二:指针被覆盖
这个坑我踩过好几次。你想想看:
int *p = (int*)malloc(100 * sizeof(int));
p = (int*)malloc(200 * sizeof(int)); // 第一次分配的地址丢了!
free(p); // 只释放了第二次的,第一次的永远泄漏了
第一次malloc的地址被覆盖了,再也找不回来。这就是典型的「丢指针」泄漏。
场景三:异常路径未释放
这个在复杂函数里特别常见:
char* read_file(const char* path) {
char *buf = (char*)malloc(1024);
FILE *fp = fopen(path, "r");
if (!fp) {
return NULL; // 泄漏了!buf没释放
}
// 正常读取...
fclose(fp);
return buf;
}
调用者如果忘记free返回值,或者函数提前返回,都会造成泄漏。
场景四:容器/链表中的泄漏
我维护过一个老项目,里面有个全局链表:
struct node {
int data;
struct node *next;
};
void add_node(int val) {
struct node *n = malloc(sizeof(struct node));
n->data = val;
n->next = head;
head = n;
}
// 但清理函数只清空了链表头指针,没逐个free节点!
void clear_list() {
head = NULL; // 所有节点都泄漏了!
}
1.4 一张图看懂内存泄漏
下面这张SVG图,帮你理清内存泄漏的核心逻辑:
1.5 避坑指南
💡 我的个人习惯:
- 每次写malloc,立刻写对应的free——这叫「成对编程」
- 在函数开头声明所有资源,在函数末尾统一释放
- 使用静态分析工具(如cppcheck、splint)自动检测
⚠️ 我曾经踩过的坑:
有一次我写了一个字符串处理函数,里面malloc了临时缓冲区。正常路径下释放了,但有个异常分支直接return了——忘了释放。这个bug在测试环境跑了两个月才被发现。从那以后,我养成了「每个return之前检查资源释放」的习惯。
1.6 小结
内存泄漏说白了就是「有借无还」。它不会立刻杀死你的程序,但会慢慢耗死它。尤其是在嵌入式领域,内存资源本来就紧张,泄漏一点就少一点。
记住三个关键点:
- 成对出现——malloc和free要成对写
- 异常路径——所有return前都要检查资源
- 工具辅助——别全靠人眼,用工具帮你查
下一章我们聊聊怎么用工具定位内存泄漏——嗯,到时候我会分享几个实战中特别好用的方法。
可可精品资料库