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

2023-12-19

根据这个答案 https://stackoverflow.com/a/40323646/1197719传递给可变参数函数的数字常量始终被视为int如果它们合二为一的话。这让我想知道为什么下面的代码适用于两者,int and 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 too?

哎呀,它甚至在交替时也能工作int and 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 and long long没有任何麻烦吗?我现在真的很困惑...

感谢您对此的任何启发!


这与 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 架构上的寄存器,以便休息,你真正的可变参数,在堆栈上传递。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么我的可变参数函数可以同时使用 int 和 long long ? 的相关文章

随机推荐