生长中技术笔记
C 语言中关于 realloc() 的思考
为什么需要 realloc()
在 C 语言中,malloc() 和 calloc() 分配内存时必须一次性指定大小。如果后续需要更多空间,只能再 malloc() 一块新内存、手动拷贝、再 free() 旧内存。realloc() 把这三步封装成一个调用,是动态内存管理中最常用的调整手段。
函数原型
在 C 标准中(自 C89 起),realloc() 的函数原型如下:
text
void *realloc(void *ptr, size_t size);它接受两个参数:
- ptr:要调整大小的内存块指针,该指针必须是由之前的
malloc/calloc/realloc分配得到的,或者是NULL。 - size:调整后的新大小(字节数)。
工作原理
realloc() 的行为取决于 ptr 和 size 的组合:
- size 比原内存块大:为内存块分配一个新的、更大的空间,将原数据拷贝过去,释放旧块,返回新指针。实现上会优先尝试在原块尾部追加(即"原地扩展"),成功则无需拷贝;失败才走"分配新块 + 拷贝 + 释放旧块"的路径。
- size 比原内存块小:直接缩小逻辑大小,通常返回同一个指针。注意:被裁掉的部分只是不再属于你,底层是否真正归还给操作系统取决于分配器实现。
- size 为 0:C99 明确规定释放该内存块并返回
NULL;C89 中此情形为实现定义行为,移植时需注意。 - ptr 为 NULL:行为等价于
malloc(size)。
关键风险与边界
失败时原指针仍然有效
如果 realloc() 分配失败,返回 NULL,但原 ptr 指向的内存块并未被释放,数据仍然完好。因此不能直接写:
c
ptr = realloc(ptr, new_size); // 若失败,ptr 变为 NULL,原地址丢失安全写法是先存到临时变量:
c
void *tmp = realloc(ptr, new_size);
if (tmp == NULL) {
/* 处理错误:ptr 仍然有效,可继续使用或 free(ptr) */
} else {
ptr = tmp;
}提醒:以上为简化示例,未处理
new_size溢出、ptr本身为NULL等边界。实际项目中应结合错误码和日志做完整处理。
其他常见陷阱
- 对已释放的指针调用
realloc():未定义行为。 - 对非堆内存(如栈变量、全局数组)的指针调用
realloc():未定义行为。 - 对齐问题:
realloc()返回的指针满足最大基本对齐要求,但如果你用posix_memalign等分配了特殊对齐的块,realloc()可能无法满足该对齐,此时应使用平台特定的对齐 realloc 接口(如 Windows 的_aligned_realloc)。 - 平台差异:"原地扩展"只是优化提示,标准并不保证;不同分配器(glibc、jemalloc、Windows CRT)策略不同,性能表现会有差异。
与动态数组扩容的关系
很多语言的动态数组(如 C++ std::vector、Python list)内部策略是:维护一个容量(capacity),当元素数量达到容量时,按一个增长因子(常见为 2 倍)申请更大的内存,把旧数据拷贝过去,释放旧块。
realloc() 在这里的价值在于:
- 优先追加:如果原块尾部恰好有足够空闲空间,分配器可以直接扩展,省去整块拷贝,降低扩容开销。
- 可裁剪:当数组经过大量删除后,容量远大于实际元素数,可以用
realloc()缩小到合理大小,节省内存。
当然,"优先追加"只是实现层面的优化,标准只保证语义正确性。是否真的避免了拷贝,取决于分配器内部状态和内存碎片情况,不能当作确定性保证。
验证建议
- 在开发阶段用 AddressSanitizer(
-fsanitize=address)或 Valgrind 检测realloc()相关的越界、双重释放、use-after-free。 - 对关键路径写单元测试,覆盖
size增大、减小、为 0、ptr为NULL、分配失败(可通过setrlimit限制地址空间模拟)等分支。 - 跨平台部署时,关注目标平台分配器文档,确认
size == 0和特殊对齐的行为是否符合预期。
小结
realloc() 是动态内存调整的核心工具,理解它的四种路径和失败语义是正确使用的前提。在动态数组等场景中,它能减少不必要的整块拷贝,但"原地扩展"只是优化而非保证。使用时务必用临时变量接收返回值,避免丢失原指针,并注意对齐和平台差异。
本文最初于 2023/8/26 发布在 CSDN。
评论
正在读取评论…