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图,帮你理清内存泄漏的核心逻辑:

内存泄漏核心逻辑图 正常流程 malloc() 申请内存 使用内存 free() 释放内存 ✅ 内存归还系统 泄漏流程 malloc() 申请内存 使用内存 ❌ 忘记 free() 💀 内存永久丢失 VS 关键区别:正常流程有 free(),泄漏流程没有 free()

1.5 避坑指南

💡 我的个人习惯:

  • 每次写malloc,立刻写对应的free——这叫「成对编程」
  • 在函数开头声明所有资源,在函数末尾统一释放
  • 使用静态分析工具(如cppcheck、splint)自动检测

⚠️ 我曾经踩过的坑:

有一次我写了一个字符串处理函数,里面malloc了临时缓冲区。正常路径下释放了,但有个异常分支直接return了——忘了释放。这个bug在测试环境跑了两个月才被发现。从那以后,我养成了「每个return之前检查资源释放」的习惯。

1.6 小结

内存泄漏说白了就是「有借无还」。它不会立刻杀死你的程序,但会慢慢耗死它。尤其是在嵌入式领域,内存资源本来就紧张,泄漏一点就少一点。

记住三个关键点:

  • 成对出现——malloc和free要成对写
  • 异常路径——所有return前都要检查资源
  • 工具辅助——别全靠人眼,用工具帮你查

下一章我们聊聊怎么用工具定位内存泄漏——嗯,到时候我会分享几个实战中特别好用的方法。


可可精品资料库