动态内存管理:malloc/calloc/realloc/free 函数详解、内存分配失败处理
说到动态内存管理,这绝对是C语言里最容易出坑的地方之一。我刚开始做嵌入式开发那会儿,就因为在堆上分配内存没处理好,导致设备跑几天就死机。排查了整整一周,最后发现是内存泄漏。嗯,今天我们就来把这几个函数彻底讲透。
malloc:最基础的内存分配器
malloc 的全称是 memory allocation。它的作用就是从堆区申请一块连续的内存空间。
void *malloc(size_t size);
返回的是 void* 指针,所以需要强制类型转换。我个人习惯是这么写的:
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL) {
// 处理分配失败
}
这里有个细节:malloc 分配的内存是未初始化的。里面存的是随机值,也就是所谓的「野数据」。你想想看,如果你直接读取这块内存,结果完全不可预测。
calloc:带清零的分配器
calloc 和 malloc 最大的区别就是:它会把分配的内存全部初始化为0。
void *calloc(size_t nmemb, size_t size);
参数是两个:元素个数和每个元素的大小。比如:
int *arr = (int *)calloc(10, sizeof(int));
// 等价于:
int *arr = (int *)malloc(10 * sizeof(int));
memset(arr, 0, 10 * sizeof(int));
我在项目中遇到过一个问题:用 malloc 分配了一个结构体数组,结果因为没初始化,里面的指针成员指向了随机地址。后来改成 calloc,问题就解决了。说白了,calloc 更安全,但速度会慢一点点。
realloc:动态调整内存大小
这个函数很有意思。它可以在不丢失原有数据的前提下,调整已分配内存的大小。
void *realloc(void *ptr, size_t new_size);
使用时要特别注意:
- 如果
ptr是 NULL,realloc的行为等同于malloc - 如果
new_size是 0,行为等同于free - 如果扩展失败,原内存不会被释放
我曾经犯过一个经典错误:
// ❌ 错误写法
int *p = (int *)malloc(10 * sizeof(int));
p = (int *)realloc(p, 20 * sizeof(int)); // 如果realloc失败,p变成NULL,原内存泄漏!
// ✅ 正确写法
int *p = (int *)malloc(10 * sizeof(int));
int *tmp = (int *)realloc(p, 20 * sizeof(int));
if (tmp != NULL) {
p = tmp; // 只有成功才赋值
} else {
// 处理失败,p仍然有效
}
free:释放内存的必修课
有分配就必须有释放。这是铁律。
void free(void *ptr);
使用 free 时有几个铁律:
- 只能释放由
malloc/calloc/realloc分配的内存 - 不能重复释放同一块内存(double free)
- 释放后要将指针置为 NULL,避免野指针
int *p = (int *)malloc(10 * sizeof(int));
// ... 使用 p ...
free(p);
p = NULL; // 好习惯!
为什么释放后要置 NULL?因为 free 只是把内存标记为可用,但指针仍然指向那块地址。如果不置 NULL,后面不小心又 free 一次,就是 double free,程序直接崩溃。
内存分配失败处理
嵌入式系统里,内存分配失败是家常便饭。堆空间有限,碎片化严重,随时可能分配失败。
处理策略一般有三种:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 检查返回值 | 每次 malloc 后都检查是否为 NULL | 所有场景,必须做 |
| 错误恢复 | 分配失败时释放其他内存,重试 | 内存紧张的系统 |
| 断言退出 | 分配失败直接 assert 或 abort | 开发调试阶段 |
我个人推荐的做法是:
void *safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
// 记录日志
fprintf(stderr, "malloc failed: size=%zu\n", size);
// 尝试释放缓存
release_cache();
// 重试一次
ptr = malloc(size);
if (ptr == NULL) {
// 实在不行就报错
exit(EXIT_FAILURE);
}
}
return ptr;
}
知识体系图
这张图把四个函数的关系和特性都串起来了。你想想看,malloc 和 calloc 是分配,realloc 是调整,free 是释放。每个函数都有自己的使用场景和注意事项。
好了,动态内存管理这块就讲到这里。记住核心原则:谁分配谁释放,每次分配都要检查失败,释放后指针置 NULL。做到这三点,大部分内存问题都能避免。
可可精品资料库