根据this answer传递给可变函数的数字常量,如果它们合适,则始终被视为int
。这让我想知道为什么以下代码适用于int
和long long
。请考虑以下函数调用:
testfunc(4, 1000, 1001, 1002, 1003);
testfunc
看起来像这样:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
int x = va_arg(marker, int);
printf("%d\n", x);
}
va_end(marker);
}
这很好用。它打印1000,1001,1002,1003。但令我惊讶的是,以下代码也可以使用:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
long long x = va_arg(marker, long long);
printf("%lld\n", x);
}
va_end(marker);
}
为什么?为什么它也适用于long long
?我认为如果数字整数常量适合于int
那么它们会被传递给它们吗? (参见上面的链接)那么它如何与long long
一起使用呢?
哎呀,它在int
和long long
之间交替工作。这让我感到困惑:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
if(k & 1) {
long long x = va_arg(marker, long long);
printf("B: %lld\n", x);
} else {
int x = va_arg(marker, int);
printf("A: %d\n", x);
}
}
va_end(marker);
}
这怎么可能?我认为我的所有参数都是int
传递的...为什么我可以在int
和long long
之间来回切换,没有任何问题?我现在真的很困惑......
感谢任何光线照射到这上面!
答案 0 :(得分:6)
这与C无关。只是你使用的系统(x86-64)传递了64位寄存器中的前几个参数,即使对于可变参数也是如此。
基本上,在您使用的体系结构上,编译器生成的代码对每个参数使用完整的64位寄存器,包括可变参数。这是ABI对架构的一致意见,与C本身无关;所有程序,无论生成多少,都应该遵循它应该运行的体系结构的ABI。
如果您使用Windows,x86-64会依次使用rcx
,rdx
,r8
和r9
作为四个第一个(整数或指针)参数,并为其余的堆叠。在Linux,BSD,Mac OS X和Solaris中,x86-64使用rdi
,rsi
,rdx
,rcx
,r8
和{{1对于前六个(整数或指针)参数,按顺序,以及其余的堆栈。
您可以使用一个简单的示例程序验证这一点:
r9
如果在Linux,BSD,Solaris或Mac OS(X或更高版本)中将上述内容编译为x86-64程序集(例如extern void func(int n, ...);
void test_int(void)
{
func(0, 1, 2);
}
void test_long_long(void)
{
func(0, 1LL, 2LL);
}
),则会得到大约(AT&amp; T语法,源代码) ,目标操作数顺序)
gcc -Wall -O2 -march=x86-64 -mtune=generic -S
即。函数是相同的,不会将参数推送到堆栈。请注意,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
,只是更简单。
但是,如果您为x86(call func; ret
)进行编译,则会得到大约
-m32 -march=i686 -mtune=generic
表明Linux / BSDs /等中的x86调用约定。涉及在堆栈上传递可变参数,并且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
变量将32位常量推送到堆栈(int
将32位常量pushl $x
推送到堆栈),并且{ {1}}变量将64位常量推送到堆栈。
因此,由于您使用的操作系统和体系结构的基础ABI,您的可变参数函数显示您观察到的“异常”。要仅查看C标准所期望的行为,您需要解决基础ABI问题 - 例如,通过启动具有至少六个参数的可变参数函数来占用x86-64体系结构上的寄存器,以便休息,你真正的可变参数,在栈上传递。