4. realloc函数:动态内存的“变形金刚”

说实话,C语言里我最喜欢的动态内存函数就是realloc。为什么?因为它太灵活了。你想想看,malloc只能分配一块新内存,free只能释放,而realloc呢?它能让你在程序运行时,把已经分配好的内存块变大或变小。这就像手里捏着一块橡皮泥,随时可以捏成你想要的大小。

我在做网络数据缓冲区的时候,经常用到它。一开始不知道数据有多大,先分配个1KB,等数据来了发现不够,直接realloc到4KB。嗯,这种“先凑合用,不够再扩”的思路,在实际项目中太常见了。

4.1 函数原型:看一眼就记住

#include <stdlib.h>

void *realloc(void *ptr, size_t new_size);

参数就两个:

  • ptr:之前用malloccallocrealloc返回的指针。如果传NULL,效果等同于malloc(new_size)
  • new_size:新内存块的大小,单位是字节。

返回值:成功返回新内存块的指针,失败返回NULL,原来的内存块保持不变。

核心要点realloc返回的指针,可能和原来的指针相同,也可能不同。千万别假设它不变!

4.2 调整内存大小:变大变小,随你心意

realloc调整内存时,内部做了三件事:

  1. 如果新大小 <= 旧大小,直接截断,多余部分释放。
  2. 如果新大小 > 旧大小,且后面有足够的连续空间,就在原地扩展。
  3. 如果新大小 > 旧大小,但后面空间不够,就找一块新的足够大的内存,把旧数据复制过去,然后释放旧内存。

说白了,它尽量在原地操作,实在不行才搬家。这种“能不动就不动”的策略,对性能很友好。

小技巧:如果你只是想释放内存,可以用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 核心逻辑流程图 调用 realloc(ptr, new_size) ptr == NULL ? 是 → 相当于 malloc(new_size) new_size == 0 ? 是 → 相当于 free(ptr) 原地可扩展 ? 是 → 原地扩展,返回原指针 否 → 分配新内存 复制数据,释放旧内存 返回新指针

从图里可以看得很清楚:realloc不是简单的“重新分配”,它内部有一套完整的决策逻辑。理解了这套逻辑,你就能预测它的行为,写出更健壮的代码。

4.5 总结

realloc是动态内存管理的瑞士军刀。它既能扩容,也能缩容,还能当mallocfree用。但越灵活的东西,越容易出错。记住三点:

  • 永远用临时变量接收返回值
  • 不要假设指针不变
  • 缩小内存时数据会丢失

做到这三点,realloc就是你手里最趁手的工具。


公众号:蓝海资料掘金营,微信deep3321