每个 PUSH 指令是否在 x64 上推送 8 字节的倍数?

Does each PUSH instruction push a multiple of 8 bytes on x64?

在 x64 上,每个 PUSH 指令是否推送 8 字节的倍数?如果不是,它推动了多少?

另外,每个函数参数消耗多少堆栈space?

不,但实际上,总是将一个 8 字节的值压入堆栈。

函数参数占用不同数量的堆栈space,具体取决于函数参数的大小以及它是在堆栈中传递、在寄存器中传递还是通过引用传递。

如果通过pushing在栈中传递一个函数参数,那么事实上有很方便的push指令可以push 8 bytes强烈建议您将参数作为 8 字节值传递。对于指针、int64 和普通双精度数,这显然很容易。对于 char、bool、short 和其他内存较小的类型,大多数编译器所做的是将值压入一个 8 字节的块中。占用 16 或 32 字节的类型可能由编译器使用多个推送指令推送。更大的价值往往不会通过推动来传递;通常,编译器会尝试将指针传递给更大的值,而不是传递值本身。 {我构建了一个可以传递任意大值的编译器,但它是通过在堆栈中创建 space,然后执行块移动指令来实现的]。详细信息因编译器而异,并且取决于正在编译的程序的语言语义。

一个真正聪明的编译器可能会注意到几个参数很小,可以打包成一个 8 字节的数量,只需要一次推送。我还没有看到有人真正这样做,可能是因为将这些值一起打包到一个寄存器中需要工作,而且推送指令在设计和缓存方面已经非常快了。

可以将较小的值压入堆栈。根据体系结构,这是合法的,但如果推送的小值集不是 8 字节的倍数,则可能会导致未对齐的访问性能下降。然后必须小心地正确弹出非倍数以恢复堆栈对齐。根据我的经验没有用(请参阅 Peter Cordes 的代码高尔夫评论)。

如果您在寄存器中传递值,则不会推送任何内容:-}

人们可能会安排将参数值存储在堆栈中众所周知的位置。然后没有任何推送:-}

64 位模式下的 PUSH 操作数大小

入栈值的大小和栈指针调整的量取决于PUSH指令的操作数大小。在 64 位模式下,操作数大小只能是 16 位或 64 位。不可能在 64 位模式下对 32 位 PUSH 指令进行编码,也不可能在任何模式下对 8 位 PUSH 指令进行编码。

例如,这些都是64位的PUSH指令:

push    rax
push    1              ; 8-bit immediate sign-extended to 64 bits
push    65536          ; 32-bit immediate sign-extended to 64 bits
push    QWORD PTR[0]
push    fs             ; 16-bit segment register zero-extended to 64 bits

以上指令都是将RSP减8,然后向RSP指向的位置写入一个64位的值

这些都是 16 位 PUSH 指令:

push    ax
push    WORD PTR[0]

这些指令将RSP减2,然后将一个16位的值写入RSP指向的位置。因为它们严重错位堆栈,所以在 64 位模式下使用 16 位 PUSH 几乎总是错误的。相反,您应该将 16 位值加载到寄存器中(如果尚未存在),根据需要扩展它,然后使用 64 位 PUSH。

以下指令是非法的,不能在 64 位模式下编码:

push    al
push    eax
push    BYTE PTR[0]
push    DWORD PTR[0]
push    0100000000h    ; 64-bit immediate value isn't supported

将 8 位或 32 位值压入堆栈需要将值加载到寄存器中,扩展它,然后使用 64 位 PUSH,就像处理 16 位值一样。

64 位模式下的参数传递

一般来说,在 64 位模式下,函数参数不会在堆栈上传递。 Microsoft 和 Linux 64 位 x86 调用约定都在寄存器中传递大多数参数。堆栈仅在寄存器中没有足够的空间将参数传递给函数时使用。在那种情况下,每个参数占用一个或多个 8 字节堆栈槽。请注意,编译器不一定会使用 PUSH 指令将这些参数放入堆栈。一个常见的策略是在函数序言中为函数的所有传出参数在堆栈上分配足够的 space,然后根据需要使用 MOV 指令将参数放入堆栈。