C 语言动态内存:realloc() 函数的扩容机制
在 C 语言中,当你使用 malloc() 或 calloc() 分配了一块堆内存后,有时会发现空间不够用。此时,不要手动复制数据并重新分配内存,而是应优先考虑使用 realloc() 函数——它能自动完成“扩容+数据迁移”的全过程。
realloc() 的基本用法
realloc() 的函数原型如下:
#include <stdlib.h>
void *realloc(void *ptr, size_t new_size);
- 第一个参数
ptr是你之前通过malloc()、calloc()或realloc()分配的内存指针。 - 第二个参数
new_size是你希望的新内存大小(以字节为单位)。 - 返回值是一个指向新内存块的指针,类型为
void*,通常需要强制转换为你实际使用的类型。
调用 realloc(ptr, new_size) 后,系统会尝试将原内存块扩展到 new_size 字节。如果成功,原数据会被完整保留,新增部分内容未定义;如果失败,返回 NULL,但原内存块保持不变,不会被释放。
扩容的三种可能情况
realloc() 在内部根据系统内存布局和请求大小,有三种处理方式:
- 原地扩容:如果原内存块后面有足够的连续空闲空间,系统直接扩展该块,不移动数据,返回原指针。
- 异地扩容:如果原位置无法扩展,系统会在堆中另找一块足够大的新内存,自动复制原数据过去,然后释放旧内存,返回新地址。
- 扩容失败:如果系统无法找到足够内存,返回
NULL,原内存仍然有效,需手动处理错误。
因此,永远不要直接写 ptr = realloc(ptr, new_size);。一旦失败,ptr 会被赋值为 NULL,导致原内存地址丢失,造成内存泄漏。
正确做法是使用临时指针接收返回值:
int *temp = realloc(ptr, new_size);
if (temp == NULL) {
// 处理错误:原 ptr 仍有效
fprintf(stderr, "realloc failed\n");
// 可选择继续使用原内存,或退出
} else {
ptr = temp; // 只有成功才更新指针
}
实际操作步骤:安全使用 realloc()
- 确保原指针合法:
ptr必须是由malloc()、calloc()或realloc()返回的有效指针,或者为NULL(此时realloc(NULL, size)等价于malloc(size))。 - 计算新大小:明确你需要多少字节。例如,若要将整型数组从
n个元素扩容到m个,则新大小为m * sizeof(int)。 - 用临时指针调用
realloc():int *new_ptr = realloc(old_ptr, m * sizeof(int)); - 检查返回值是否为
NULL:- 如果是
NULL,不要释放old_ptr,也不要继续使用新内存。 - 如果不是
NULL,将old_ptr更新为new_ptr。
- 如果是
- 继续使用新内存:此时
ptr指向的内存至少包含原数据,可安全访问前min(原大小, 新大小)字节。 - 最终记得
free(ptr):无论是否扩容成功,只要内存不再需要,就应调用free()释放。
常见误区与注意事项
| 问题 | 正确做法 |
|---|---|
直接赋值 ptr = realloc(ptr, size); |
使用临时指针判断是否成功后再赋值 |
对栈变量或全局变量指针调用 realloc() |
只能对堆内存(malloc 系列分配的)使用 |
| 忽略返回值类型转换(C++ 中) | C 语言可隐式转换,但为清晰建议显式转,如 (int*)realloc(...) |
| 认为新分配的额外内存会被清零 | realloc() 不会初始化新增部分,如需清零,应配合 memset() 或改用 calloc() 初始分配 |
扩容性能与内存碎片
realloc() 的效率取决于是否发生“异地迁移”。频繁的小幅扩容(如每次只增加几个字节)容易导致:
- 多次内存复制,降低性能;
- 产生内存碎片,使后续大块分配失败。
建议策略:采用“指数增长”方式扩容。例如,初始分配 4 个元素,不够时扩为 8,再不够扩为 16……这样均摊下来,每次插入的平均成本是常数级别(即“摊还分析”中的 O(1))。
示例代码:
#include <stdio.h>
#include <stdlib.h>
int main() {
size_t capacity = 4;
size_t size = 0;
int *arr = malloc(capacity * sizeof(int));
if (!arr) return 1;
for (int i = 0; i < 20; i++) {
if (size >= capacity) {
capacity *= 2; // 指数扩容
int *temp = realloc(arr, capacity * sizeof(int));
if (!temp) {
fprintf(stderr, "Out of memory\n");
free(arr);
return 1;
}
arr = temp;
}
arr[size++] = i;
}
for (size_t i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
此程序动态维护一个整数数组,自动扩容,避免了频繁重分配。
特殊情况:缩小内存
realloc() 也可用于缩小已分配内存。例如:
ptr = realloc(ptr, smaller_size);
此时,系统可能:
- 直接截断多余部分(原地操作);
- 或者什么都不做(因为释放少量尾部内存收益低)。
但无论如何,前 smaller_size 字节的数据一定保留,超出部分不可访问。
总结关键原则
- 永远用临时指针接收
realloc()返回值。 - 仅对堆内存使用
realloc(),不可用于栈或静态变量。 - 新增内存未初始化,勿直接读取。
- 失败时不释放原内存,需自行处理错误。
- 合理设计扩容策略,避免线性增长导致性能下降。
// 安全模板
void *safe_realloc(void *ptr, size_t new_size) {
void *temp = realloc(ptr, new_size);
if (temp == NULL && new_size > 0) {
// 分配失败且不是释放操作
// 此处可根据需求 abort() 或记录错误
return NULL;
}
return temp;
}
暂无评论,快来抢沙发吧!