在 x64 系统(Windows)中,函数调用默认使用一种快速调用(Fastcall)约定。它主要依靠寄存器来传递前几个参数,以提高效率,只有参数过多时才会使用堆栈。
它的核心规则可以概括为一张表:
| 参数位置 | 参数类型 | 传递使用的寄存器 |
|---|---|---|
| 第1个参数 | 整数/指针 | RCX |
| 浮点数 | XMM0 | |
| 第2个参数 | 整数/指针 | RDX |
| 浮点数 | XMM1 | |
| 第3个参数 | 整数/指针 | R8 |
| 浮点数 | XMM2 | |
| 第4个参数 | 整数/指针 | R9 |
| 浮点数 | XMM3 | |
| 第5个及以后 | 所有类型 | 堆栈(从右到左压栈) |
详细规则与特殊情况
整数与浮点参数的“专车专用”:前四个整数参数和浮点参数使用的是两套完全独立的寄存器,互不干扰。例如,当一个函数的前两个参数是
int和float时,它们会分别使用RCX和XMM1寄存器。大型参数(结构体等)通过引用传递:参数大小不是 1、2、4 或 8 字节,或者本身是
__m128类型、数组和字符串时,不会尝试直接放入寄存器,而是在内存中复制一份,然后将指向这份拷贝的指针放入对应的寄存器或堆栈中。这可以理解为“传引用”。例外:大小恰好是 8、16、32 或 64 位的结构体,会像整数一样,直接按值传递到对应的寄存器中。
“影子空间”(Shadow Store):一个很有趣的约定。调用方在堆栈上始终要预留 32 字节(4个寄存器 × 8字节)的空间,即使被调用的函数用不到这么多参数。这主要是为了在需要时,被调用方可以方便地将寄存器参数保存回堆栈,简化了对可变参数(varargs)等复杂情况的支持。
寄存器保存规则:在整个调用过程中,一些寄存器的值需要被被调用方(Callee)保护(即使用前先保存,返回前恢复),如
RBX,RBP,RDI,RSI,R12-R15,XMM6-XMM15。而另一些则被认为是易失的(Volatile),调用方(Caller)可以假设它们在函数返回后已被修改,包括用于传参的RCX,RDX,R8,R9,XMM0-XMM5以及RAX,R10,R11等。这保证了函数调用后关键数据不会丢失。