4. realloc函数:动态内存的“变形金刚”
说实话,C语言里我最喜欢的动态内存函数就是realloc。为什么?因为它太灵活了。你想想看,malloc只能分配一块新内存,free只能释放,而realloc呢?它能让你在程序运行时,把已经分配好的内存块变大或变小。这就像手里捏着一块橡皮泥,随时可以捏成你想要的大小。
我在做网络数据缓冲区的时候,经常用到它。一开始不知道数据有多大,先分配个1KB,等数据来了发现不够,直接realloc到4KB。嗯,这种“先凑合用,不够再扩”的思路,在实际项目中太常见了。
4.1 函数原型:看一眼就记住
#include <stdlib.h>
void *realloc(void *ptr, size_t new_size);
参数就两个:
- ptr:之前用
malloc、calloc或realloc返回的指针。如果传NULL,效果等同于malloc(new_size)。 - new_size:新内存块的大小,单位是字节。
返回值:成功返回新内存块的指针,失败返回NULL,原来的内存块保持不变。
核心要点:realloc返回的指针,可能和原来的指针相同,也可能不同。千万别假设它不变!
4.2 调整内存大小:变大变小,随你心意
realloc调整内存时,内部做了三件事:
- 如果新大小 <= 旧大小,直接截断,多余部分释放。
- 如果新大小 > 旧大小,且后面有足够的连续空间,就在原地扩展。
- 如果新大小 > 旧大小,但后面空间不够,就找一块新的足够大的内存,把旧数据复制过去,然后释放旧内存。
说白了,它尽量在原地操作,实在不行才搬家。这种“能不动就不动”的策略,对性能很友好。
小技巧:如果你只是想释放内存,可以用realloc(ptr, 0)。不过我个人建议还是用free,语义更清晰,别人读代码也更容易理解。
代码示例:动态数组扩容
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = NULL;
int capacity = 2;
int count = 0;
// 初始分配
arr = (int*)malloc(capacity * sizeof(int));
if (!arr) {
perror("malloc failed");
return 1;
}
// 模拟不断添加数据
for (int i = 0; i < 10; i++) {
if (count >= capacity) {
capacity *= 2; // 每次翻倍扩容
int *temp = (int*)realloc(arr, capacity * sizeof(int));
if (!temp) {
perror("realloc failed");
free(arr);
return 1;
}
arr = temp;
printf("扩容到 %d 个元素\n", capacity);
}
arr[count++] = i * 10;
}
// 打印结果
for (int i = 0; i < count; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
输出结果:
扩容到 4 个元素
扩容到 8 个元素
扩容到 16 个元素
0 10 20 30 40 50 60 70 80 90
看到了吗?每次容量不够就翻倍,realloc帮我们搞定了扩容和复制。这种“指数扩容”策略,在项目里很常见——既不会频繁扩容影响性能,也不会浪费太多内存。
4.3 注意事项:踩过的坑,你别再踩
嗯,这里我要多说几句。realloc虽然好用,但坑也不少。我曾经在一个嵌入式项目里,就因为没处理好返回值,导致内存泄漏,查了两天才找到原因。
警告:千万别这样写!
// 错误写法:如果realloc失败,原指针丢失!
ptr = realloc(ptr, new_size);
如果realloc返回NULL,原来的内存块不会被释放,但ptr已经被覆盖成NULL了。旧内存就永远找不回来了——内存泄漏!
正确的做法是:
// 正确写法:用临时变量接收返回值
void *new_ptr = realloc(ptr, new_size);
if (new_ptr == NULL) {
// 处理失败,ptr仍然有效
perror("realloc failed");
free(ptr); // 或者继续使用旧内存
return;
}
ptr = new_ptr; // 安全更新
其他注意事项
- 不要对栈上的指针用
realloc——它只能处理堆内存。我见过有人对局部数组指针用realloc,程序直接崩溃。 - 缩小内存时,多余的数据会丢失。如果你缩小到一半大小,后半部分的数据就没了。这不是错误,是设计如此。
- 频繁小幅度扩容效率低。每次
realloc都可能涉及复制,建议一次多扩一些,比如翻倍或加固定大小。 - 多线程环境下要加锁。堆管理器不是线程安全的,多个线程同时
realloc同一块内存,后果不堪设想。
我的习惯:每次realloc后,我都会把旧指针置为NULL,防止误用。虽然有点啰嗦,但能避免很多诡异的bug。
4.4 知识体系:realloc的核心逻辑
下面这张图,帮你把realloc的整个流程串起来。我画图时特意把“原地扩展”和“搬家”两种情况分开,这样你一看就明白。
从图里可以看得很清楚:realloc不是简单的“重新分配”,它内部有一套完整的决策逻辑。理解了这套逻辑,你就能预测它的行为,写出更健壮的代码。
4.5 总结
realloc是动态内存管理的瑞士军刀。它既能扩容,也能缩容,还能当malloc和free用。但越灵活的东西,越容易出错。记住三点:
- 永远用临时变量接收返回值
- 不要假设指针不变
- 缩小内存时数据会丢失
做到这三点,realloc就是你手里最趁手的工具。