这与 C 无关。只是您使用的系统 (x86-64) 在 64 位寄存器中传递前几个参数,即使对于可变参数也是如此。
本质上,在您使用的体系结构上,编译器生成的代码对每个参数(包括可变参数)使用完整的 64 位寄存器。这是架构上约定的ABI,与C本身无关;所有程序,无论如何生成,都应该遵循其应运行的架构上的 ABI。
如果您使用 Windows,则 x86-64 使用rcx
, rdx
, r8
, and r9
按顺序存储前四个(整数或指针)参数,其余的则堆栈。在 Linux、BSD、Mac OS X 和 Solaris 中,x86-64 使用rdi
, rsi
, rdx
, rcx
, r8
, and r9
按顺序存储前六个(整数或指针)参数,其余参数则堆栈。
您可以使用一个简单的示例程序来验证这一点:
extern void func(int n, ...);
void test_int(void)
{
func(0, 1, 2);
}
void test_long_long(void)
{
func(0, 1LL, 2LL);
}
如果将以上内容编译为 x86-64 程序集(例如gcc -Wall -O2 -march=x86-64 -mtune=generic -S
)在 Linux、BSD、Solaris 或 Mac OS(X 或更高版本)中,您大约会得到(AT&T 语法,源、目标操作数顺序)
test_int:
movl $2, %edx
movl $1, %esi
xorl %edi, %edi
xorl %eax, %eax
jmp func
test_long_long:
movl $2, %edx
movl $1, %esi
xorl %edi, %edi
xorl %eax, %eax
jmp func
即功能是相同的,并且不要将参数压入堆栈。注意jmp func
相当于call func; ret
,就更简单了。
但是,如果您针对 x86 进行编译(-m32 -march=i686 -mtune=generic
),你大约得到
test_int:
subl $16, %esp
pushl $2
pushl $1
pushl $0
call func
addl $28, %esp
ret
test_long_long:
subl $24, %esp
pushl $0
pushl $2
pushl $0
pushl $1
pushl $0
call func
addl $44, %esp
ret
这表明 Linux/BSDs/etc 中的 x86 调用约定。涉及在堆栈上传递可变参数,并且int
变体将 32 位常量推入堆栈(pushl $x
压入 32 位常量x
到堆栈),以及long long
变体将 64 位常量压入堆栈。
因此,由于您使用的操作系统和架构的底层 ABI,您的可变参数函数显示了您观察到的“异常”。要仅看到您期望从 C 标准获得的行为,您需要解决底层 ABI 怪癖 - 例如,通过使用至少六个参数启动可变参数函数,以占用 x86-64 架构上的寄存器,以便休息,你真正的可变参数,在堆栈上传递。