选择在x64通用的四个参数寄存器 - 适用于UN*X / Win64
x86需要注意的一点是,寄存器名称到“寄存器号”编码并不明显;就指令编码而言(MOD R/M字节,参见http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm),寄存器号0...7分别对应?AX
,?CX
,?DX
,?BX
,?SP
,?BP
,?SI
,?DI
。
因此,将A / C / D(寄存器0..2)选为返回值和前两个参数(这是“传统”32位__fastcall
约定)是一个合乎逻辑的选择。就64位而言,“更高”的寄存器被排序,Microsoft和UN*X / Linux都选择了R8
/ R9
作为第一个寄存器。
记住这一点,Microsoft选择使用RAX
(返回值)和RCX
、RDX
、R8
、R9
(arg[0..3])是一个可以理解的选择,如果你选择四个寄存器作为参数。
我不知道为什么AMD64 UN*X ABI在RCX
之前选择了RDX
。
选择在x64上特定于UN*X的六个参数寄存器
在RISC架构上,UN*X传递参数通常使用寄存器 - 特别地,在前六个参数上(至少在PPC、SPARC、MIPS上如此)。这可能是AMD64(UN*X)ABI设计者选择在该架构上也使用六个寄存器的主要原因之一。
因此,如果您想要使用六个寄存器来传递参数,并且选择使用RCX
、RDX
、R8
和R9
作为其中四个寄存器,则应该选择哪两个?
“高级”寄存器需要额外的指令前缀字节来选择它们,因此指令大小会更大,因此如果有其他选择,您不会选择它们。由于RBP
和RSP
具有隐含的含义,因此这些寄存器不可用,而RBX
在UN*X上传统上具有特殊用途(全局偏移表),似乎AMD64 ABI设计者不想让它与之相冲突。
因此,唯一的选择是RSI
/ RDI
。
因此,如果您必须将RSI
/ RDI
作为参数寄存器,那么应该选择哪些参数?
将它们设为arg[0]
和arg[1]
具有某些优点。请参见cHao的评论。
?SI
和
?DI
是字符串指令的源操作数和目标操作数。正如 cHao 所提到的,它们作为参数寄存器的使用意味着在 AMD64 UN*X 调用约定中,最简单的
strcpy()
函数只包含两个 CPU 指令
repz movsb; ret
,因为调用者已经将源/目标地址放入正确的寄存器中。特别是在低级别和编译器生成的“粘合”代码(例如,一些 C++ 堆分配器在构造时对对象进行零填充,或内核对
sbrk()
的堆页面进行零填充,或写时复制页错误),存在大量的块复制/填充,因此对于经常使用的代码来说,保存本来会加载这样的源/目标地址参数到“正确”寄存器的两三个 CPU 指令将非常有用。
因此,在某种程度上,UN*X 和 Win64 只有一个区别,即 UN*X 在有意选择的RSI
/RDI
寄存器中添加了两个额外的参数,来代替自然选择的四个参数RCX
、RDX
、R8
和 R9
。
除此之外 ...
UN*X 和 Windows x64 ABIs 之间还有更多的差异,而不仅仅是将参数映射到特定的寄存器。有关 Win64 的概述,请查看:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64和AMD64的UN*X在使用栈空间方面也有显著的差异;例如,在Win64上,调用者必须为函数参数分配栈空间,即使参数0…3已经通过寄存器传递了。另一方面,在UN*X上,如果叶函数(即不调用其他函数的函数)所需的栈空间不超过128字节,甚至都不需要分配栈空间(是的,您拥有并可以使用一定量的栈而无需分配它…除非您是内核代码,那就是一个麻烦的错误源)。所有这些都是特定的优化选择,其中大部分理由都在原始帖子的维基百科参考中解释。