本文介绍了为什么我的可变参数函数可以同时使用 int 和 long long?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据 this answer 传递给可变参数函数的数字常量总是被视为 int 如果它们合二为一.这让我想知道为什么下面的代码同时适用于 intlong long.考虑以下函数调用:

According to this answer numeric constants passed to variadic functions are always treated as int if they fit in one. This makes me wonder why the following code works with both, int and long long. Consider the following function call:

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.但令我惊讶的是,以下代码也能正常工作:

This works fine. It prints 1000, 1001, 1002, 1003. But to my surprise, the following code works as well:

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 呢?

Why is that? Why does it work with long long too? I thought that numeric integer constants were passed as int if they fit in one? (cf. link above) So how can it be that it works with long long too?

哎呀,它甚至在 intlong long 之间交替时也能工作.这让我很困惑:

Heck, it's even working when alternating between int and long long. This is confusing the heck out of me:

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

How can this be? I thought all my parameters were passed as int... why can I arbitrarily switch back and forth between int and long long with no trouble at all? I'm really confused now...

感谢您对此有所了解!

推荐答案

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

That has nothing to do with C. It is just that the system you used (x86-64) passes the first few arguments in 64-bit registers, even for variadic arguments.

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

Essentially, on the architecture you used, the compiler produces code that uses a full 64-bit register for each argument, including variadic arguments. This is the ABI agreed upon the architecture, and has nothing to do with C per se; all programs, no matter how produced, are supposed to follow the ABI on the architecture it is supposed to run.

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

If you use Windows, x86-64 uses rcx, rdx, r8, and r9 for the four first (integer or pointer) arguments, in that order, and stack for the rest. In Linux, BSD's, Mac OS X, and Solaris, x86-64 uses rdi, rsi, rdx, rcx, r8, and r9 for the first six (integer or pointer) arguments, in that order, and stack for the rest.

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

You can verify this with a trivial example program:

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 操作数顺序)

If you compile the above to x86-64 assembly (e.g. gcc -Wall -O2 -march=x86-64 -mtune=generic -S) in Linux, BSDs, Solaris, or Mac OS (X or later), you get approximately (AT&T syntax, source,target operand order)

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,更简单.

i.e. the functions are identical, and do not push the arguments to the stack. Note that jmp func is equivalent to call func; ret, just simpler.

但是,如果您针对 x86 进行编译 (-m32 -march=i686 -mtune=generic),您将获得大约

However, if you compile for x86 (-m32 -march=i686 -mtune=generic), you get approximately

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 位常量压入堆栈.

which shows that the x86 calling conventions in Linux/BSDs/etc. involve passing the variadic arguments on stack, and that the int variant pushes 32-bit constants to the stack (pushl $x pushes a 32-bit constant x to the stack), and the long long variant pushes 64-bit constants to the stack.

因此,由于您使用的操作系统和架构的底层 ABI,您的可变参数函数显示了您观察到的异常".要单独从 C 标准中看到您期望的行为,您需要解决底层的 ABI 怪癖——例如,通过使用至少六个参数启动可变参数函数,以占用 x86-64 架构上的寄存器,以便rest,您真正的可变参数,在堆栈上传递.

Therefore, because of the underlying ABI of the operating system and architecture you use, your variadic function shows the "anomaly" you observed. To see the behaviour you expect from the C standard alone, you need to work around the underlying ABI quirk -- for example, by starting your variadic functions with at least six arguments, to occupy the registers on x86-64 architectures, so that the rest, your truly variadic arguments, are passed on the stack.

这篇关于为什么我的可变参数函数可以同时使用 int 和 long long?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-01 09:12