了解堆栈对齐强制执行

眠りネ*_*ネロク 5 x86 assembly gcc abi memory-alignment

考虑以下 C 代码:

#include <stdint.h>

void func(void) {
   uint32_t var = 0;
   return;
}
Run Code Online (Sandbox Code Playgroud)

-O0GCC 4.7.2 为上述代码生成的未优化(即:选项)汇编代码是:

func:
    pushl %ebp
    movl %esp, %ebp
    subl $16, %esp
    movl $0, -4(%ebp)
    nop
    leave
    ret
Run Code Online (Sandbox Code Playgroud)

根据堆栈对准要求所述的系统V ABI,堆栈必须由16个字节每之前对准call指令(该堆栈边界是默认的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
Run Code Online (Sandbox Code Playgroud)

考虑到堆栈的这种表示,有两点让我感到困惑:

  1. var显然在堆栈上没有对齐到 16 个字节。这个问题似乎是矛盾的时候,我看过这个答案这个问题(重点是我自己的):

    -mpreferred-stack-boundary=n编译器尝试将堆栈上的项目与2^对齐n

    在我的情况下-mpreferred-stack-boundary没有提供,因此根据GCC 文档的这一部分(我确实得到了与 相同的结果-mpreferred-stack-boundary=4),它默认设置为 4(即:2^4=16 字节边界)。

  2. 在堆栈上分配 16 个字节(即:subl $16, %esp指令)而不是仅分配 8 个字节的目的:在分配 16 个字节后,堆栈既没有按 16 个字节对齐,也没有保留任何内存空间。通过只分配 8 个字节,堆栈按 16 个字节对齐,并且不会浪费额外的 8 个字节。

Flo*_*mer 5

查看-O0生成的机器代码通常是徒劳的。编译器将以最简单的方式发出任何有效的结果。这通常会导致奇怪的伪影。

栈对齐仅指栈帧的对齐。它与堆栈上对象的对齐方式没有直接关系。GCC 将以所需的对齐方式分配堆栈上的对象。如果 GCC 知道堆栈帧已经提供了足够的对齐,那么这会更简单,但如果没有,GCC 将使用帧指针并执行显式对齐。