为什么 "lea..and..push" 汇编代码经常出现在函数的开头?

Why do "lea..and..push" assembly codes frequently come up at the beginning of a function?

我发现我在通过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(可以与任何为局部变量保留堆栈space的代码合并)
    • 实际需要16字节对齐的数据可以给16字节对齐而不用自己对齐栈
  • 上面的
  • none 可以证明是正确的,所以函数必须假设 "worst case" 并强制自己对齐(例如你在函数开始)

当然,对于一个好的编译器来说,最后一种情况(需要您显示的代码)是极其罕见的。

但是;大多数编译器都不好,因为它们无法看到整个程序(如果程序被分成多个单独编译的目标文件,那么编译器一次只能看到程序的一小部分)。他们无法弄清楚调用图的 much/any,因此最后一种情况(需要您显示的代码)变得非常普遍。要解决这个问题,您需要 "link time code generation",但通常人们不会费心。

注意:对于 AVX2,您需要 32 字节对齐,对于 AVX512,您需要 64 字节对齐,对于某些事情(为了避免在大量线程代码中错误共享),您可能需要 "cache line size alignment" (通常也是 64 字节对齐)。这使得 "examine call graph to determine what alignment is actually needed" 算法比我描述的要复杂一些。