动态分配字符数组场景下strcpy函数工作异常原因排查
问题分析:两种C代码内存越界行为差异的原因
首先看第一段代码及其运行表现:
#include <stdio.h> #include <stdlib.h> #include <string.h> void cpy(char *str) { //char buffer[16]; char *buffer = malloc(16 * sizeof(char)); strcpy(buffer, str); for (int i = 0; i < 300; i++) { printf("buffer[%i]: %c \n", i, buffer[i]); } } int main() { //char large_str[256]; char *large_str = malloc(256 * sizeof(char)); for (int i = 0; i < 256; i++) { large_str[i] = 'A'; } //large_str[255]= '\0'; cpy(large_str); return 0; }
在Cygwin环境编译运行上述代码后,
buffer[0]至buffer[299]大多为空,仅部分索引区间(如buffer[252]至buffer[264])存在乱码,buffer[0]至buffer[23]为'A'。
再看修改后的代码及运行结果:
#include <stdio.h> #include <stdlib.h> #include <string.h> char buffer[16]; void cpy(char *buffer, char *str) { //char *buffer = malloc(16 * sizeof(char)); strcpy(buffer, str); for (int i = 0; i < 300; i++) { printf("buffer[%i]: %c \n", i, buffer[i]); } } int main() { //char large_str[256]; char *large_str = malloc(256 * sizeof(char)); for (int i = 0; i < 256; i++) { large_str[i] = 'A'; } //large_str[255]= '\0'; cpy(buffer, large_str); return 0; }
运行结果完全正常,
buffer[0]至buffer[255]均为'A',其余索引为空。
核心原因:内存越界的未定义行为 + 不同内存区域的布局差异
两种代码都存在严重的内存越界问题——strcpy会一直写入直到遇到\0,但large_str没有设置终止符,所以会写入256个'A',远超过buffer的16字节容量。之所以表现不同,是因为buffer所在的内存区域完全不一样:
1. 第一种代码:堆内存越界的不可预测性
buffer是通过malloc分配的堆内存。堆内存由C标准库的分配器管理,为了维护内存块的分配状态,分配器会在每个堆块的前后添加元数据(比如块大小、空闲标记),而且通常会有对齐要求,实际分配的内存可能比你请求的16字节稍大(比如你看到的前23个'A',说明分配器给了至少24字节的块)。- 当你越界写入时,会破坏堆的管理元数据,甚至覆盖其他堆内存区域的内容。后续访问
buffer[i]超过分配块的部分时,你其实在读取堆里的空闲区域、已被其他分配的内存,或者被破坏的元数据,这些内容完全不确定,所以会出现乱码、空值等随机表现。
2. 第二种代码:静态存储区越界的“看似正常”
buffer是全局数组,存储在静态存储区。这个区域的内存是程序启动时就分配好的,默认初始化为0,而且通常是一块连续的大内存块,在你的程序里暂时没有其他全局变量占用后面的空间。- 越界写入时,你只是在静态存储区里连续写入256个'A',后面的内存还是初始的0值,所以看起来“正常”。但这依然是未定义行为——如果后续添加其他全局变量,你会覆盖它们的值,导致程序出现莫名其妙的bug,而且不同编译器、不同系统下的静态存储区布局可能完全不同,表现也会不一样。
关键提醒
永远不要依赖未定义行为的表现!正确的做法是:
- 确保目标缓冲区足够容纳要拷贝的内容(包括终止符
\0) - 使用带长度限制的字符串函数,比如
strncpy(注意手动添加终止符),或者更安全的strlcpy(部分系统支持) - 如果使用动态分配,记得根据源字符串的长度来分配足够的内存
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

