考虑以下 C 代码:
#include <stdint.h>
void func(void) {
uint32_t var = 0;
return;
}
未优化的(即:-O0
选项)GCC 4.7.2为上述代码生成的汇编代码是:
func:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $0, -4(%ebp)
nop
leave
ret
根据堆栈对齐要求 of the 系统V ABI,堆栈必须在每个之前对齐 16 个字节call
指令(堆栈边界当不使用选项更改时,默认为 16 字节-mpreferred-stack-boundary
)。因此,结果为ESP
modulo在函数调用之前 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 字节对齐。这个问题似乎与我读到的内容相矛盾在这个答案中 to 这个问题(重点是我自己的):
-mpreferred-stack-boundary=n
编译器试图保留的地方堆栈上的项目对齐 to 2^n
.
就我而言-mpreferred-stack-boundary
未提供,因此默认设置为 4(即:2^4=16 字节边界)GCC 文档的这一部分(我确实得到了相同的结果-mpreferred-stack-boundary=4
).
在堆栈上分配 16 个字节的目的(即:subl $16, %esp
指令)而不是只分配 8 个字节:分配 16 个字节后,堆栈既不会按 16 字节对齐,也不会节省任何内存空间。通过仅分配 8 个字节,堆栈将按 16 字节对齐,并且不会浪费额外的 8 个字节。
看着-O0
生成机器代码通常是徒劳的。编译器将以最简单的方式发出任何有效的结果。这通常会导致奇怪的伪影。
栈对齐仅指栈帧的对齐。它与堆栈上对象的对齐方式没有直接关系。 GCC 将以所需的对齐方式分配堆栈上的对象。如果 GCC 知道堆栈帧已经提供了足够的对齐,那么这会更简单,但如果没有,GCC 将使用帧指针并执行显式对齐。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)