C 库函数 – realloc():动态内存的“变形金刚”
在 C 语言中,动态内存管理是程序员必须掌握的核心技能之一。当我们需要在程序运行时根据实际需求分配内存时,malloc() 和 calloc() 常常是起点。但现实情况往往是:一开始申请的内存大小不够,或者太浪费。这时候,C 库函数 realloc() 就像一位“变形金刚”——它能帮你在不丢失原有数据的前提下,灵活地调整已分配内存的大小。
你可能已经用过 malloc() 分配一块内存,比如:
int *arr = (int *)malloc(5 * sizeof(int));
但如果你后来发现需要存储 10 个整数,再调用一次 malloc() 并复制数据,未免太麻烦。realloc() 就是为解决这类问题而生的。它允许你在原有内存块的基础上“扩容”或“缩容”,省去了手动拷贝的繁琐。
realloc() 的基本语法与返回值
realloc() 函数的原型定义在 <stdlib.h> 头文件中,其函数签名如下:
void *realloc(void *ptr, size_t new_size);
ptr:指向之前通过malloc()、calloc()或realloc()分配的内存块的指针。new_size:新的内存大小(以字节为单位)。- 返回值:成功时返回指向新内存块的指针;失败时返回
NULL,且原内存块保持不变。
⚠️ 重要提醒:
realloc()不会自动初始化新内存,也不保证原内存块会被保留。如果失败,旧内存仍然有效,但你不能再使用ptr指针访问它,除非你保留了原始指针。
扩容:从 5 个整数到 10 个整数
我们来看一个典型的扩容场景。假设你一开始只申请了 5 个整数的空间,后来发现不够用,想扩展到 10 个。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 第一步:分配 5 个整数的空间
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 初始化前 5 个元素
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
// 输出当前内容
printf("扩容前:");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 第二步:使用 realloc 扩容到 10 个整数
int *new_arr = (int *)realloc(arr, 10 * sizeof(int));
// 检查 realloc 是否成功
if (new_arr == NULL) {
printf("内存扩容失败!\n");
free(arr); // 释放原始内存,防止内存泄漏
return 1;
}
// 成功后,new_arr 指向新的内存块
// 原来的数据仍然保留,但 arr 已失效,必须使用 new_arr
arr = new_arr;
// 继续初始化后 5 个元素
for (int i = 5; i < 10; i++) {
arr[i] = i + 1;
}
// 输出扩容后的内容
printf("扩容后:");
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 最后释放内存
free(arr);
return 0;
}
代码解析:
realloc(arr, 10 * sizeof(int))试图将arr指向的内存从 20 字节(5×4)扩展到 40 字节(10×4)。- 如果系统内存足够,
realloc()会尝试在原内存块后直接扩展,效率高。 - 如果无法扩展,它会分配一块新的内存,将原数据复制过去,然后释放旧内存。
- 返回的新指针必须赋值给变量(如
new_arr),否则你会丢失对新内存的引用。 - 关键点:即使扩容成功,也不能再使用旧指针
arr,必须用realloc返回的新指针。
缩容:释放多余内存,节约资源
realloc() 不仅可以扩容,还能“缩容”。这在处理动态数据结构时特别有用。比如你申请了 100 个整数的空间,但实际只用了 30 个,就可以通过 realloc() 释放多余部分。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 申请 100 个整数的空间
int *arr = (int *)malloc(100 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 只使用前 30 个
for (int i = 0; i < 30; i++) {
arr[i] = i * 2;
}
// 用 realloc 缩容到 30 个整数
int *shrinked_arr = (int *)realloc(arr, 30 * sizeof(int));
if (shrinked_arr == NULL) {
printf("缩容失败!\n");
free(arr);
return 1;
}
// 缩容成功后,arr 指针更新
arr = shrinked_arr;
// 输出缩容后的内容
printf("缩容后前 10 个元素:");
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 释放内存
free(arr);
return 0;
}
💡 形象比喻:把内存比作一个房间。
malloc()是租了一个大房间,realloc()就像你发现房间太大了,于是搬进一个更小的房间,同时把必要的东西搬过去,剩下的空间退还给房东。
realloc() 的行为机制:内部如何工作?
realloc() 并不是每次都“在原地”扩展。它的行为取决于系统内存的布局,主要有两种情况:
| 情况 | 说明 |
|---|---|
| 原地扩容 | 如果原内存块后面有连续的空闲空间,realloc() 会直接扩展,不移动数据,效率最高。 |
| 重新分配并复制 | 如果没有连续空间,realloc() 会分配一块新内存,复制原有数据,然后释放旧内存。 |
这意味着,realloc() 返回的指针可能和原指针不同。你绝不能假设 ptr 会一直有效。
常见错误与最佳实践
❌ 错误 1:忘记检查返回值
// 错误示例
arr = realloc(arr, 20 * sizeof(int)); // 没有判断返回值
// 如果失败,arr 变成了 NULL,后续使用会崩溃
✅ 正确做法:
int *temp = realloc(arr, 20 * sizeof(int));
if (temp == NULL) {
printf("内存扩容失败!\n");
free(arr); // 释放原内存,避免泄漏
return 1;
}
arr = temp; // 更新指针
❌ 错误 2:重复释放内存
free(arr);
realloc(arr, 100); // 无效操作,arr 已释放
✅ 正确做法:realloc() 会自动释放旧内存(如果成功),但你必须确保只释放一次。
实际应用:动态字符串缓冲区
realloc() 在处理字符串时特别有用。比如你从用户输入中读取文本,但不知道长度,可以动态扩容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *read_line() {
char *buffer = NULL;
size_t size = 0;
size_t capacity = 16; // 初始容量
buffer = (char *)malloc(capacity);
if (buffer == NULL) return NULL;
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
if (size >= capacity - 1) {
// 扩容:翻倍
capacity *= 2;
char *new_buffer = (char *)realloc(buffer, capacity);
if (new_buffer == NULL) {
free(buffer);
return NULL;
}
buffer = new_buffer;
}
buffer[size++] = ch;
}
// 添加结束符
buffer[size] = '\0';
return buffer;
}
int main() {
printf("请输入一行文字:");
char *line = read_line();
if (line != NULL) {
printf("你输入的内容是:%s\n", line);
free(line);
} else {
printf("读取失败!\n");
}
return 0;
}
这个例子展示了 realloc() 在流式输入处理中的强大能力。每次缓冲区不够,就自动翻倍扩容,既高效又节省内存。
总结:掌握 realloc() 的核心要点
realloc()是 C 库函数 – realloc() 的核心功能,让你能动态调整已分配内存的大小。- 它可以扩容、缩容,甚至在某些情况下“搬家”。
- 必须检查返回值,失败时不要继续使用原指针。
- 返回值可能与原指针不同,必须重新赋值。
- 不要重复释放内存,
realloc()成功后会自动释放旧内存。 - 适合用于动态数组、字符串缓冲、数据结构扩展等场景。
掌握 realloc(),就像拥有了一个“内存橡皮泥”——你可以随心所欲地塑形,但前提是懂得规则,否则会“捏崩”程序。
在 C 语言的内存管理世界里,realloc() 是连接静态规划与动态需求的桥梁。熟练运用它,你将写出更高效、更灵活的代码。