C语言局部无终止符字符串打印异常:为何输出结果有差异?
关于C语言联合体与局部变量内存布局的疑惑
代码与现象
带联合体的代码
#include <stdio.h> union Data { char var1; char var2; char varArr[10]; }; int main() { union Data data; data.var1 = 'a'; data.var2 = 'b'; data.varArr[0] = 'c'; char ctr[3]; ctr[0] = data.var1; ctr[1] = data.var2; ctr[2] = data.varArr[0]; printf("Result: %s\n", ctr); return 0; }
- 题目预期输出为
ccc,但ctr是无\0终止符的3字节数组,按printf("%s")的逻辑会读取到第一个\0才停止,本应输出随机内容,但实际多次运行始终输出ccc。 - 检查
ctr[3]、ctr[4]等越界字节,发现通常为\0,偶尔是0x01这类小值,但输出仍为ccc。
简化后的代码
#include <stdio.h> int main() { char ctr[3]; ctr[0] = 'c'; ctr[1] = 'c'; ctr[2] = 'c'; printf("Result: %s\n", ctr); return 0; }
- 每次运行输出
cccX(X为随机变化的垃圾值)。
内存布局层面的解释(基于gcc 13、64位x86-64架构)
核心前提
x86-64架构下,栈内存从高地址向低地址增长,gcc默认按变量声明顺序从高到低分配局部变量(先声明的变量地址更高),同时栈帧需满足16字节对齐要求。
带联合体代码的栈布局
- 联合体的内存特性:
union Data的大小由最大成员varArr[10]决定,为10字节,且所有成员共享同一块内存的起始地址。因此data.var1、data.var2、data.varArr[0]指向同一个字节,最后赋值的data.varArr[0] = 'c'会覆盖之前的'a'和'b',这是ctr三个元素均为'c'的原因。 - 局部变量的分配顺序:
- 先声明的
data被分配在栈的高地址区域,占用10字节内存。 - 后声明的
ctr被分配在data的低地址侧,占用3字节内存。 - 由于栈帧16字节对齐要求,编译器会在
data和ctr之间或data末尾添加3字节填充,使总局部变量空间对齐到16字节。
- 先声明的
- 越界字节的来源:
ctr的越界字节(如ctr[3])紧邻data的内存区域,这部分内存要么是编译器添加的填充字节(默认初始化为\0),要么是data中未被赋值的剩余字节(栈上未初始化内存通常残留小值,如0x01这类非打印ASCII字符)。printf("%s")输出时,遇到\0会停止;遇到非打印字符时,终端不会显示,因此最终只呈现可见的ccc。
简化代码的差异
简化代码中没有data变量,ctr直接分配在栈帧中,其越界字节指向的是之前栈帧残留的随机数据,这些数据更可能包含可打印ASCII字符(如X),因此会被终端显示出来,形成cccX的输出。
内容的提问来源于stack exchange,提问作者pnguib
相关产品推荐
相关产品推荐

