c - 为什么我的可变参数函数同时适用于 int 和 long long?

标签 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 之间任意来回切换而没有问题全部?我现在真的很困惑......

感谢您对此有所了解!

最佳答案

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

本质上,在您使用的架构上,编译器生成的代码对每个参数(包括可变参数)使用完整的 64 位寄存器。这是架构约定的ABI,与C本身无关;所有程序,无论是如何生成的,都应该在它应该运行的架构上遵循 ABI。

如果您使用 Windows,x86-64 使用 rcxrdxr8r9 作为四个第一个(整数或指针)参数,按此顺序,其余的堆栈。在 Linux、BSD、Mac OS X 和 Solaris 中,x86-64 使用 rdirsirdxrcxr8r9 用于前六个(整数或指针)参数,按此顺序,其余的入栈。

您可以使用一个简单的示例程序来验证这一点:

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/

相关文章:

c - 在 Visual Studio 2012 中使用 GNU 科学库的问题

c - 使用什么数据结构来模拟一个可以在任何位置添加数据的数组?

用于双字符串比较的 C case 语句

C++ - Va_List(可变数字参数)未正确转换

java - vararg 参数可以是 <? super 对象>?

c - 字符串和指针操作

使用命令行参数在 C 程序中创建多个文件

java - "type X[][] should explicitly be cast to Object[][] for the invocation of the varargs"

我们可以调用 va_start() 两次而不调用 va_end() 吗?

java - Vararg 方法覆盖/重载混淆