根据 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
之间任意来回切换而没有问题全部?我现在真的很困惑......
感谢您对此有所了解!
最佳答案
这与 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
和 r9
用于前六个(整数或指针)参数,按此顺序,其余的入栈。
您可以使用一个简单的示例程序来验证这一点:
extern void func(int n, ...);
void test_int(void)
{
func(0, 1, 2);
}
void test_long_long(void)
{
func(0, 1LL, 2LL);
}
如果您在 Linux、BSD、Solaris 或 Mac OS 中将以上编译为 x86-64 程序集(例如 gcc -Wall -O2 -march=x86-64 -mtune=generic -S
) (X 或更高版本),你得到大约(AT&T 语法,source,target 操作数顺序)
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 体系结构上的寄存器,以便其余的,您真正的可变参数,在堆栈上传递。
关于c - 为什么我的可变参数函数同时适用于 int 和 long long?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40330749/