为何打印变量x的内存地址会影响缓冲区溢出结果?
问题分析:打印变量地址改变缓冲区溢出效果的原因
这个问题的核心是编译器的栈布局优化与变量位置调整——当你使用&x取变量地址时,编译器会改变x在栈上的存储位置,让它不再和字符串数组str相邻,自然就不会被溢出覆盖了。
1. 对比两种场景的栈布局差异
我们直接从汇编代码里拆解变量的存储位置:
无printf("%p: ", &x)的场景(代码1)
从汇编的赋值指令可以明确看到:
- 字符串数组
str存储在25(%esp)起始的位置(movl $1953719636, 25(%esp)对应"Testt"的前4字节ASCII值) - 字符变量
x存储在31(%esp)的位置(movb $88, 31(%esp),88是'X'的ASCII码)
str数组实际占6字节("Testt" + 终止符\0),刚好占据25(%esp)到30(%esp)的空间,和x所在的31(%esp)直接相邻。当你执行strcpy(str, "Hello world")(字符串含终止符共12字节)时,溢出的字节会直接覆盖到x的存储位置,所以x被改成了'w'。
有printf("%p: ", &x)的场景(代码2)
汇编里的赋值指令显示变量位置发生了明显变化:
str数组存储在26(%esp)起始的位置- 字符变量
x存储在25(%esp)的位置
此时x位于str的栈地址前方(栈空间是从高地址向低地址增长,但数组内部是从低到高存储),而strcpy的溢出是向高地址方向写入,只会覆盖str后方的栈空间,完全碰不到在它前面的x,所以x的值保持为'X'不变。
2. 为什么取&x会触发布局变化?
编译器在处理局部变量的栈布局时,会根据变量的使用场景做优化:
- 当你没有取
x的地址时,编译器认为x是一个可被灵活放置的局部变量,为了栈对齐或空间利用效率,会把它放在str的后方,刚好形成相邻布局。 - 当你使用
&x获取地址时,x必须拥有一个稳定的内存地址(要传递给printf作为参数),编译器会调整它的位置——可能是为了规避溢出风险,也可能是为了满足16字节栈对齐规则(汇编里的andl $-16, %esp就是强制栈对齐到16字节边界),最终把x放到了str的前方,躲开了溢出范围。
3. 从运行结果验证结论
两段代码的运行输出也能直接对应布局变化:
- 代码1中
str的地址是0061FF29,x在它后方的相邻地址,溢出后被覆盖; - 代码2中
str的地址是0061FF2A,x的地址是0061FF29,明确在str的地址前方,溢出写不到该位置。
这个现象也侧面说明:局部变量的栈布局是编译器自主决定的,没有固定顺序,依赖栈布局的缓冲区溢出行为完全不可靠——这也是现代编译器默认开启栈保护(Stack Smashing Protector)的原因之一。
内容的提问来源于stack exchange,提问作者Wes Weitzel
相关产品推荐
相关产品推荐

