C 堆栈分配、填充和对齐

C 堆栈分配、填充和对齐,c,gcc,assembly,x86,stack,C,Gcc,Assembly,X86,Stack,我一直在努力深入了解编译器如何生成机器代码,更具体地说,GCC如何处理堆栈。在这样做的过程中,我一直在编写简单的C程序,将它们编译成汇编,并尽我所能理解结果。下面是一个简单的程序及其生成的输出: asmtest.c: void main() { char buffer[5]; } asmtest.s: pushl %ebp movl %esp, %ebp subl $24, %esp leave ret 令我困惑的是,为什么要为堆栈分配24个字节。我知道,由于处理器如

我一直在努力深入了解编译器如何生成机器代码,更具体地说,GCC如何处理堆栈。在这样做的过程中,我一直在编写简单的C程序,将它们编译成汇编,并尽我所能理解结果。下面是一个简单的程序及其生成的输出:

asmtest.c

void main() {
    char buffer[5];
}
asmtest.s

pushl   %ebp
movl    %esp, %ebp
subl    $24, %esp
leave
ret
令我困惑的是,为什么要为堆栈分配24个字节。我知道,由于处理器如何寻址内存,堆栈必须以4的增量分配,但如果是这种情况,我们应该只将堆栈指针移动8个字节,而不是24个字节。作为参考,一个17字节的缓冲区会产生一个移动了40字节的堆栈指针,而没有缓冲区会移动堆栈指针8。1到16字节(含)之间的缓冲区移动
ESP
24字节

现在假设8个字节是一个必要的常量(需要它做什么?),这意味着我们正在以16个字节的块进行分配。为什么编译器会以这种方式对齐?我使用的是x86_64处理器,但即使是64位字也只需要8字节对齐。为什么会有差异


作为参考,我在运行10.5、GCC4.0.1且未启用优化的Mac上编译了这篇文章。

我发现,在页面底部有一些关于堆栈可能更大的合理解释。将概念扩展到64位机器,它可能会解释您看到的内容。

之所以有8个字节,是因为第一条指令将%ebp的起始值推送到堆栈上(假设为64位)

这是一个由
-mprefered stack boundary=n
控制的gcc特性,编译器试图使堆栈上的项与
2^n
对齐。如果将
n
更改为
2
,它将只在堆栈上分配8个字节。
n
的默认值为
4
,即它将尝试与16字节边界对齐


为什么会有“默认”8个字节,然后是24=8+16个字节,是因为堆栈已经包含8个字节用于
leave
ret
,因此编译后的代码必须首先将堆栈调整8个字节,以使其与2^4=16对齐。

,您可能会发现这很有趣。

SSEx指令系列要求压缩的128位向量与16字节对齐,否则您在尝试加载/存储它们时会遇到segfault。也就是说,如果您希望安全地传递16字节向量,以便在堆栈上与SSE一起使用,那么堆栈需要始终保持与16字节向量对齐。默认情况下,GCC对此负责。

Mac OS X/Darwin x86 ABI需要16字节的堆栈对齐。在其他x86平台(如Linux、Win32、FreeBSD…)上并非如此。

返回地址和基指针都被推送到堆栈上。实际的ABI要求堆栈在函数调用边界处对齐16字节。这是真的,但是,由于函数序言/尾声是堆栈指针更改的唯一位置,这几乎等同于说它需要始终对齐。“push%ebp”是否使esp减少了8字节?加上ret的8个字节,应该已经与16个字节对齐了。为什么编译器需要额外的8个字节?哦,我知道了。这是一台32位机器。很抱歉它应该是ret 4字节+ebp 4字节+对齐的8字节+缓冲区16当前版本的i386和x86-64 System V ABI需要16B堆栈对齐(在调用
指令之前),因此允许函数假设。从历史上看,i386 ABI只需要4B校准。(有关ABI文档的链接,请参阅)。GCC还保持
%esp
对齐,即使在叶函数(不调用其他函数)中,当它必须保留任何空间时,这就是这里发生的事情。我可能对这件事经验太少,无法声称您的答案是错误的。但是,难道您不使用
movupd
和类似的未对齐指令来实现这一目的(加载/存储未对齐的压缩数据)吗?据我所知,当试图对未对齐的数据使用
movapd
和类似的指令时,您可能会出现错误行为,但数据未对齐一般不会有问题。@andree:
movups
在Core2和更早版本上速度较慢,即使数据已对齐。ABI是在所有CPU都是这样的时候设计的。此外,aligned允许您
paddxmm0,[rsp]
而不需要单独的
movdqu
指令。参见相关:,推理同样适用于i386 SysV ABI,以及gcc的
-mprefered stack boundary
默认设置,即在i386 SysV ABI正式更改为要求/保证它之前,32位代码的默认设置为16字节。奇怪的是,我尝试过同样的代码,使用
-mprefered stack boundary=4
,但
esp
仅减去16。相关:-
sub$8,%esp
应重新对齐堆栈,并使这8个字节可用于阵列。额外的16是一个gcc遗漏的优化。