通过动态分配获得的堆内存若在使用完毕后未释放,且程序仍持有或已丢失指向该块的指针,则发生内存泄漏。短期运行的程序退出后操作系统会回收进程资源,但长期运行的服务若持续泄漏,可用内存将逐渐减少,可能导致性能下降或运行失败。

栈与堆的分工

进程内存中,自动变量通常位于栈,函数返回时随栈帧销毁;malloc、calloc 等分配的块位于堆,须显式调用 free 释放。

内存泄漏特指已分配堆块在仍被进程占用的情况下无法再通过有效指针释放的情形。

典型示例

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *ptr = (int *)malloc(5 * sizeof(int));

    if (ptr == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    ptr[0] = 10;
    ptr[1] = 20;
    printf("Values: %d %d\n", ptr[0], ptr[1]);

    /* free(ptr);  未调用则本进程生命周期内该块无法归还 */

    return 0;
}

输出:

Values: 10 20

程序逻辑可正常结束,但对应堆块在退出前一直占用;若在循环或服务主循环中反复发生,泄漏量将累积。

常见原因

  • 遗漏 free:分配与释放未配对。
  • 指针被覆盖或失效:尚未 free 便将指针赋新值或超出作用域,原块无法再释放。
  • 循环内重复分配:每次迭代分配新块却未释放旧块。
for (int i = 0; i < 1000; i++) {
    char *buf = (char *)malloc(1024);
    /* 使用 buf … */
    /* 若未 free(buf),多次迭代将泄漏大量内存 */
}

预防措施

  • 为每次 malloc/calloc 明确对应的释放点。
  • 更换指针所指块之前,先释放旧块;free 后可将指针置为 NULL。
  • 多步分配失败时,可采用goto 统一清理或封装资源管理函数。
  • 在支持的环境中使用 Valgrind 等工具定位泄漏调用栈。

后果

  • 可用物理内存或交换空间减少,影响同机其他进程。
  • 分配器元数据与碎片增多,程序可能变慢。
  • 极端情况下触发内存不足,导致分配失败或进程被终止。

本节强调了动态内存须配对释放。下一节介绍标准库与 errno 相关的错误处理方式。