【发布时间】:2017-11-21 10:45:56
【问题描述】:
考虑以下 C 代码:
#include <stdint.h>
void func(void) {
uint32_t var = 0;
return;
}
GCC 4.7.2 为上述代码生成的未优化(即:-O0 选项)汇编代码为:
func:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $0, -4(%ebp)
nop
leave
ret
根据System V ABI的堆栈对齐要求,堆栈必须在每条call指令之前对齐16个字节(堆栈边界 默认为 16 个字节,当不使用选项 -mpreferred-stack-boundary 更改时)。因此,ESP 模 16 的结果在函数调用之前必须为零。
考虑到这些堆栈对齐要求,我假设在执行 leave 指令之前的以下堆栈状态表示是正确的:
Size (bytes) Stack ESP mod 16 Description
-----------------------------------------------------------------------------------
| . . . |
------------------........0 at func call
4 | return address |
------------------.......12 at func entry
4 | saved EBP |
----> ------------------........8 EBP is pointing at this address
| 4 | var |
| ------------------........4
16 | | |
| 12 | |
| | |
----> ------------------........8 after allocating 16 bytes
考虑到堆栈的这种表示,有两点让我感到困惑:
-
var显然没有在堆栈上对齐到 16 个字节。这个问题似乎与我从in this answer 到this question 阅读的内容相矛盾(重点是我自己的):-mpreferred-stack-boundary=n,其中编译器尝试将堆栈上的项目保持对齐到 2^n。在我的情况下,
-mpreferred-stack-boundary没有提供,因此根据this section of GCC's documentation,它默认设置为 4(即:2^4=16 字节边界)(我确实得到了与-mpreferred-stack-boundary=4相同的结果) . 在堆栈上分配 16 个字节(即:
subl $16, %esp指令)而不是仅分配 8 个字节的目的:在分配 16 个字节后,堆栈既没有按 16 个字节对齐,也没有节省任何内存空间。通过仅分配 8 个字节,堆栈将对齐 16 个字节,不会浪费额外的 8 个字节。
【问题讨论】:
-
这与 C 关系不大,与“x86”后编译为机器码的“System V ABI”关系很大。
-
好吧,作为源代码,您提供了一些非常基本的东西,它可以移植到几乎任何语言并生成相同的机器代码,所以...我建议您删除 C标签,或者在 n1570 中找到一些引用,其中提到了诸如“堆栈对齐”和“System V ABI”之类的东西......
-
另外请记住,C 编译器没有义务以任何指标生成最佳代码,包括堆栈空间使用情况。虽然它会努力(并且在 godbolt 上使用 gcc 4.7.2 看起来不错,垃圾空间只是对齐的结果),但如果它失败并分配比实际需要多 16B 的垃圾,则没有语言破坏问题(尤其是在未优化的代码中)。它遵循的(由于 platform specific 选项)是在下一个
call指令时正确对齐esp。从 C 语言的角度来看,即使堆栈存在也不是强制性的,也不是必须对齐的。
标签: gcc assembly x86 memory-alignment abi