为什么我的可变参数函数同时使用int和long long?

时间:2016-10-30 15:43:18

标签: c variadic-functions

根据this answer传递给可变函数的数字常量,如果它们合适,则始终被视为int。这让我想知道为什么以下代码适用于intlong 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一起使用呢?

哎呀,它在intlong 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传递的...为什么我可以在intlong long之间来回切换,没有任何问题?我现在真的很困惑......

感谢任何光线照射到这上面!

1 个答案:

答案 0 :(得分:6)

这与C无关。只是你使用的系统(x86-64)传递了64位寄存器中的前几个参数,即使对于可变参数也是如此。

基本上,在您使用的体系结构上,编译器生成的代码对每个参数使用完整的64位寄存器,包括可变参数。这是ABI对架构的一致意见,与C本身无关;所有程序,无论生成多少,都应该遵循它应该运行的体系结构的ABI。

如果您使用Windows,x86-64会依次使用rcxrdxr8r9作为四个第一个(整数或指针)参数,并为其余的堆叠。在Linux,BSD,Mac OS X和Solaris中,x86-64使用rdirsirdxrcxr8和{{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体系结构上的寄存器,以便休息,你真正的可变参数,在栈上传递。