C语言realloc缩减内存后旧内容残留及堆溢出问题咨询
问题现象
使用realloc缩减内存分配大小后,程序仍会打印原字符串的全部内容,直到手动在字符串末尾添加空字节(NUL)才停止;开启-fsanitize=address编译选项时,会触发heap-buffer-overflow错误。
核心疑问
既然realloc会释放多余的内存块,为何上述代码会出现溢出问题?此外,有人称无法确定realloc是否一定会释放多余块,也有人说无需关心旧内容——既然无需关心旧内容,为何代码仍会打印全部旧内容?(使用C99标准)
示例代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define NUL '\0' void check_allocation_success(char *ptr); int main(void) { char *str1, *str2; str1 = malloc(sizeof(char)*17); check_allocation_success(str1); strncpy(str1, "0123456789ABCDEF", 16); str2 = realloc(str1, 8); check_allocation_success(str2); printf("%s", str2); free(str2); return 0; } void check_allocation_success(char *ptr) { if (ptr == NULL) { printf("%s : %d : allocation failed.\n", __FILE__, __LINE__); exit(EXIT_FAILURE); } }
问题解析
1. 为何能打印全部旧内容?
realloc缩减内存时,只是告知内存分配器:程序不再需要超出新大小的内存区域,但它不会主动清除旧数据。这些数据可能暂时留在原内存位置,直到被其他内存操作覆盖——这就是你能看到完整旧字符串的原因。但注意:访问超出新分配大小的内存属于未定义行为,不能依赖这种现象,不同分配器、系统或编译选项都可能导致不同结果。
2. 堆溢出错误的原因
printf("%s", str2)会持续读取内存直到遇到NUL字节。你的原字符串通过strncpy复制了16个字符,没有自动添加NUL终止符;realloc将内存缩减到8字节后,只有前8字节是程序合法可访问的范围。printf继续读取第9字节及以后的非法内存,直接触发了AddressSanitizer的溢出检测——这是完全合理的,因为你确实访问了不属于当前分配的内存。
3. 关于realloc是否释放多余内存的说明
C99标准对realloc的定义是:当新大小小于原大小时,实现可选择释放多余内存,也可保留原内存块,但程序仅能合法访问新大小范围内的区域。也就是说,不能假设多余内存一定会被释放或保留,标准只保证新大小内的数据有效,超出部分的状态是未定义的。
正确解决方式
在realloc后,必须手动在新内存的末尾添加NUL终止符,确保字符串的合法性:
str2 = realloc(str1, 8); check_allocation_success(str2); // 仅保留前7个可见字符,第8字节放置NUL终止符 str2[7] = NUL;
这样printf只会读取合法的7个字符,既不会触发溢出错误,也不会依赖未定义的旧数据。
内容的提问来源于stack exchange,提问作者Bishnu Chalise

