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

Dem*_*emi 7 assembly x86-64 calling-convention

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

此外,每个函数参数消耗多少堆栈空间?

Ros*_*dge 10

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
Run Code Online (Sandbox Code Playgroud)

上述指令都从RSP中减去8,然后将64位值写入RSP指向的位置.

这些都是16位PUSH指令:

push    ax
push    WORD PTR[0]
Run Code Online (Sandbox Code Playgroud)

这些指令从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
Run Code Online (Sandbox Code Playgroud)

在堆栈上按下8位或32位值需要将值加载到寄存器中,然后使用64位PUSH进行扩展,就像使用16位值一样.

参数在64位模式下传递

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


Ira*_*ter 1

不,但实际上,人们总是将 8 字节值压入堆栈。

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

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

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

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

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

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