让我们看一个具体的例子。macOS使用x86-64的标准System V ABI。ABI中有大量的细节,但我们将专注于基础知识。
参数是在寄存器中传递的。整数参数在寄存器rdi、rsi、rdx、rcx、r8和r9中传递,按顺序排列。浮点参数在SSE寄存器xmm0到xmm7中传递。当调用一个变量函数时,寄存器 al 被设置为用于传递参数的SSE寄存器的数量。整数返回值被放在rax和rdx中,浮点返回值被放在xmm0和xmm1中。
变参函数的ABI几乎与普通函数的ABI相同。唯一的例外是传递al中使用的SSE寄存器的数量。然而,当使用变参函数 ABI调用普通函数时,这一点是无害的,因为普通函数会忽略al的内容。
C语言把事情搞得有点乱。C语言规定,当作为变量参数传递时,某些类型会被提升为更广泛的类型。小于int的整数(如char和short)被提升为int,而float被提升为double。如果你的方法签名中包括这些类型之一,那么如果调用者使用变量原型,就不可能将参数传递为该确切的类型。
对于整数来说,这实际上并不重要。整数被存储在相应的寄存器的底层位,无论哪种方式,其位数最终都在同一个地方。然而,这对浮点数来说是灾难性的。将一个较小的整数转换为int只需要用额外的位来填充它。将float转换为double涉及到将数值完全转换为一个不同的结构。float中的位与double中的相应位并不一致。如果你试图使用一个变量原型来调用一个接受浮动参数的非变量函数,该函数将收到垃圾。
To illustrate this problem, here's a quick example:
// Use the old variadic prototype for objc_msgSend.