C 库函数 – realloc()(实战指南)

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() 是连接静态规划与动态需求的桥梁。熟练运用它,你将写出更高效、更灵活的代码。