我发现当我通过GDB查看一些文件时,很多时候,函数的开头有这三行代码
0x08048548 <+0>: lea ecx,[esp+0x4]
0x0804854c <+4>: and esp,0xfffffff0
0x0804854f <+7>: push DWORD PTR [ecx-0x4]
我通常会忽略它们,因为在创建这三行堆栈帧之后,这就是函数通常启动的方式。
谢谢。
最佳答案
这会将堆栈指针对齐到 16 字节边界,因为有时(对于 SSE)CPU 需要数据的 16 字节对齐。
一个好的编译器会检查调用图(找出什么调用什么),并决定:
函数本身不需要堆栈对齐,也不会调用其他需要堆栈对齐的函数;因此不需要堆栈对齐
所有函数的调用者都使用对齐的堆栈,因此:
- 该函数只需要进行固定调整即可重新建立预先存在的对齐方式,例如
sub esp, 8
(可以与任何为局部变量保留堆栈空间的代码合并) - 真正需要16字节对齐的数据可以给予16字节对齐,而不需要对齐堆栈本身
- 该函数只需要进行固定调整即可重新建立预先存在的对齐方式,例如
以上都不能被证明是正确的,因此函数必须假设“最坏情况”并自行强制对齐(例如,您在函数开头看到的指令)
当然,对于一个好的编译器来说,最后一种情况(需要您显示的代码)是极其罕见的。
但是;大多数编译器都不好,因为它们无法看到整个程序(如果程序被分成多个单独编译的目标文件,那么编译器一次只能看到程序的一小部分)。他们无法弄清楚大部分/任何调用图,因此最后一种情况(需要您显示的代码)变得非常常见。要解决这个问题,您需要“链接时间代码生成”,但通常人们不会打扰。
注意:对于 AVX2,您需要 32 字节对齐,对于 AVX512,您需要 64 字节对齐,对于某些事情(为了避免在多线程代码中出现错误共享),您可能需要“缓存行大小对齐”(通常还需要64 字节对齐)。这使得“检查调用图以确定实际需要什么对齐”算法比我所描述的稍微复杂一些。
关于linux - 为什么 "lea..and..push"汇编代码经常出现在函数的开头?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59331096/