在x64上,每个PUSH指令是否会推送8个字节的倍数?如果没有,它会推动多少?
此外,每个函数参数消耗多少堆栈空间?
答案 0 :(得分:8)
堆栈上推送的值的大小以及堆栈指针的调整量取决于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,然后将64位值写入RSP指向的位置。
这些都是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位模式下,函数参数不会在堆栈上传递。 Microsoft和Linux 64位x86调用约定都会在寄存器中传递大多数参数。仅当寄存器中没有足够的空间将参数传递给函数时才使用堆栈。在这种情况下,每个参数占用一个或多个8字节堆栈槽。请注意,编译器不一定使用PUSH指令将这些参数放在堆栈中。一个常见的策略是在函数序言中为函数的所有传出参数分配足够的空间,然后根据需要使用MOV指令将参数放在堆栈上。
答案 1 :(得分:0)
不,但在实践中,总是将8字节值压入堆栈。
函数参数消耗不同数量的堆栈空间,具体取决于函数参数的大小,以及它是在堆栈中传递,在寄存器中传递还是通过引用传递。
如果通过推送传递堆栈中的函数参数,那么有方便的推送指令强烈推送8个字节的事实表明您将参数传递为一个8字节的值。对于指针,int64和普通双打,这显然很容易。对于char,bool,short和其他内存大小较小的类型,大多数编译器所做的是将值推入一个8字节的块中。采用16或32字节的类型可能会被编译器用几个推送指令推送。较大的价值往往不会被推动;通常,编译器会尝试将指针传递给更大的值,而不是传递值本身。 {我已经构建了一个可以传递任意大值的编译器,但它是通过在堆栈中创建空间,然后执行块移动指令来实现的。详细信息因编译器而异,并且根据正在编译的程序的语言语义而定。
一个非常聪明的编译器可能会注意到几个参数很小并且可以打包成8字节的数量,只需要一次推送。我之前没有看到过这样做,可能是因为将这些值组合到一个寄存器中需要工作,并且按设计和缓存推送指令已经非常快了。
可以将较小的值压入堆栈。根据体系结构这是合法的,但如果推送的小值集合不是8字节的倍数,则可能导致错误对齐的访问性能命中。然后必须小心地正确弹出非多重来恢复堆栈对齐。根据我的经验没有用(参见Peter Cordes的代码高尔夫评论)。
如果您在寄存器中传递值,则不会推送任何内容: - }
可以安排将参数值存储在堆栈中众所周知的位置。然后没有任何推动: - }