11、动态内存管理(stdlib.h):malloc、calloc、realloc、free。内存泄漏、野指针、内存碎片。
动态内存管理,说白了就是让程序在运行时「按需要内存」。
我刚开始学C语言时,觉得静态数组够用了。直到有一次写一个图像处理程序,图片大小是运行时才知道的——嗯,那时候我才意识到,没有动态内存,很多事根本做不了。
11.1 四个核心函数
stdlib.h 里提供了四个函数,它们构成了C语言动态内存的基石。我习惯把它们分成两组:分配组和释放组。
11.1.1 malloc —— 最常用的分配器
void *malloc(size_t size);
malloc 分配一块连续内存,返回首地址。注意:它不会初始化内存,里面是随机值。
我在项目中遇到过一个问题:用 malloc 分配了一个结构体数组,直接拿来用,结果里面有个指针成员是野的,程序跑起来就崩。排查了半天,才发现是没清零。
11.1.2 calloc —— 带清零的分配器
void *calloc(size_t nmemb, size_t size);
calloc 和 malloc 的区别有两点:
- 它会把分配的内存全部初始化为 0
- 它接受两个参数:元素个数和每个元素的大小
我个人习惯:如果分配的内存要存字符串或结构体,我倾向于用 calloc。省得自己再写 memset。但如果你对性能有极致要求,而且马上会覆盖所有数据,那 malloc 更合适。
11.1.3 realloc —— 调整已有内存块
void *realloc(void *ptr, size_t new_size);
realloc 可以扩大或缩小之前分配的内存。我经常用它来实现动态数组——数组满了就翻倍扩容。
// 正确做法
void *new_ptr = realloc(old_ptr, new_size);
if (new_ptr == NULL) {
// 处理错误,old_ptr 仍然有效
} else {
old_ptr = new_ptr;
}
11.1.4 free —— 释放内存
void free(void *ptr);
free 把内存还给系统。但注意:free 之后,指针的值还在,它指向的内存已经无效了。这就是野指针的来源之一。
我曾经犯过一个错:free 了一个指针,后来又用这个指针去访问数据。程序偶尔正常,偶尔崩溃,查了两天才找到原因。从那以后,我每次 free 完都会把指针置为 NULL。
free(ptr);
ptr = NULL; // 避免野指针
11.2 三大陷阱
动态内存管理之所以难,不是因为函数本身复杂,而是因为人容易犯错。我总结了三个最常见的坑。
11.2.1 内存泄漏
内存泄漏就是:你申请了内存,但忘了释放。程序跑得越久,占用的内存越多,最后系统扛不住。
我见过最夸张的一次:一个服务器程序跑了三天,内存从 50MB 涨到 8GB,最后 OOM 被内核杀了。查代码发现是一个循环里每次 malloc 都没 free。
- 写代码时,先写 free,再写 malloc。养成「谁申请谁释放」的习惯
- 使用工具检测:Valgrind 是 Linux 下的神器
- 复杂项目中,可以考虑引用计数或垃圾回收库
11.2.2 野指针
野指针是指向「已释放内存」或「未分配内存」的指针。它比 NULL 指针更危险——NULL 指针至少会立即崩溃,野指针可能让你在错误的地方写数据,破坏其他变量。
野指针的常见来源:
- free 之后没有置 NULL
- 返回局部变量的地址
- 指针越界访问
11.2.3 内存碎片
内存碎片是个更隐蔽的问题。频繁地 malloc 和 free 小块内存,会导致堆上出现大量不连续的小空闲块。虽然总空闲空间够,但找不到一块连续的大内存来满足分配请求。
我做过一个嵌入式项目,内存只有 64KB。程序跑着跑着,malloc 突然返回 NULL,但统计空闲总量还有 20KB。一查,碎片化严重,最大连续块只有 3KB。
缓解碎片的方法:
- 尽量分配固定大小的内存块(对象池模式)
- 避免频繁分配释放
- 使用 slab 分配器或伙伴系统(在自定义内存管理时)
11.3 知识体系图
下面这张图总结了动态内存管理的核心脉络。我建议你把它记在脑子里,写代码时随时对照。
11.4 避坑指南
最后,分享几条我这些年总结的经验:
- 分配后立即检查 NULL —— 不要偷懒,嵌入式环境尤其重要
- free 后立即置 NULL —— 我吃过亏,你也别重蹈覆辙
- 不要多次 free 同一个指针 —— 这是未定义行为,后果不可预测
- 谁申请谁释放 —— 模块间传递内存所有权时,一定要约定清楚
- 用 Valgrind 定期检查 —— 我每周至少跑一次,能发现很多隐藏问题
动态内存管理是C语言的精髓,也是很多bug的根源。掌握好这四函数、三陷阱,你就能写出更健壮的程序。嗯,多练练,慢慢就有感觉了。