GCC是否会省略栈内存分配?x86_64下异常问题排查
GCC 12.2.0 x86_64平台下的未初始化警告与栈分配异常问题
问题概述
在x86_64平台使用GCC 12.2.0编译x64代码时,出现以下异常:
- 启用16位内存填充分支时,编译器触发未初始化警告,且生成的汇编中栈空间分配不足,无法容纳结构体;
- 切换到8位内存填充分支时,警告完全消失,栈分配正常;
- 禁用优化后,警告消失,栈分配恢复正常。
最小复现代码
#include <stdint.h> #include <stdbool.h> struct foobar_t { uint8_t data[512]; }; void my_memset(void *target) { #if 1 // 256次16位赋值填充内存 for (int i = 0; i < 256; i++) { ((uint16_t*)target)[i] = 0xabcd; } #else // 512次8位赋值填充内存 for (int i = 0; i < 512; i++) { ((uint8_t*)target)[i] = 0xab; } #endif } int main() { struct foobar_t foobar; my_memset(&foobar); if (foobar.data[123] == 0) { volatile int x = 0; } return 0; }
编译现象
启用16位赋值分支(#if 1)
执行编译命令:
gcc -O3 -fno-stack-protector -Wall -c -o x.o x.c
触发以下警告:
x.c:46:24: warning: ‘foobar’ is used uninitialized [-Wuninitialized] 46 | if (foobar.data[123] == 0) {
启用8位赋值分支(#if 0)
执行相同编译命令,无任何警告,编译正常。
异常汇编分析
启用16位分支时生成的汇编代码存在明显问题:
0000000000000000 <my_memset>: 0: f3 0f 1e fa endbr64 4: 66 0f 6f 05 00 00 00 movdqa 0x0(%rip),%xmm0 # c <my_memset+0xc> c: 48 8d 87 00 02 00 00 lea 0x200(%rdi),%rax 13: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 18: 0f 11 07 movups %xmm0,(%rdi) 1b: 48 83 c7 10 add $0x10,%rdi 1f: 48 39 f8 cmp %rdi,%rax 22: 75 f4 jne 18 <my_memset+0x18> 24: c3 ret 0000000000000030 <main>: 30: f3 0f 1e fa endbr64 34: 48 81 ec a0 01 00 00 sub $0x1a0,%rsp # 仅分配416字节栈空间 3b: 66 0f 6f 05 00 00 00 movdqa 0x0(%rip),%xmm0 # 43 <main+0x13> 43: 48 8d 44 24 98 lea -0x68(%rsp),%rax 48: 48 8d 94 24 98 01 00 lea 0x198(%rsp),%rdx 50: 0f 29 00 movaps %xmm0,(%rax) 53: 48 83 c0 10 add $0x10,%rax 57: 48 39 c2 cmp %rax,%rdx 5a: 75 f4 jne 50 <main+0x20> 5c: 80 7c 24 13 00 cmpb $0x0,0x13(%rsp) 61: 75 08 jne 6b <main+0x3b> 63: c7 44 24 94 00 00 00 movl $0x0,-0x6c(%rsp) 6b: 31 c0 xor %eax,%eax 6d: 48 81 c4 a0 01 00 00 add $0x1a0,%rsp 74: c3 ret
注意main函数中仅分配了0x1a0(416字节)的栈空间,远小于foobar_t结构体所需的512字节,存在栈溢出风险。
原因分析
这是GCC 12.2.0的别名分析优化bug导致的:
- 当通过
uint16_t*指针对内存进行赋值时,GCC的别名分析模块错误地认为这种16位赋值不会覆盖到后续以uint8_t类型访问的foobar.data[123]。实际上,data[123]是某个16位单元的低字节,会被((uint16_t*)target)[61](对应data[122]和data[123])的赋值操作覆盖。 - 基于这个错误判断,GCC认为
foobar.data[123]从未被初始化,因此触发-Wuninitialized警告。 - 同时,优化器错误地认为
foobar结构体只有部分内存被实际使用,因此仅分配了416字节的栈空间,而非完整的512字节。 - 当使用
uint8_t*指针逐个字节赋值时,GCC能正确识别所有内存都被修改,因此不会触发警告,也会分配足够的栈空间。 - 禁用优化(
-O0)时,编译器不进行激进的别名分析和栈优化,因此行为恢复正常。
内容的提问来源于stack exchange,提问作者Clara Höfner
相关产品推荐
相关产品推荐

