Why does my variadic function work with int and long long?

As per this answer, numeric constants passed to variational functions are always treated as int

if they fit into 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

as follows:

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); 
}

      

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

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); 
}

      

Why? Why does he work with long long

too? I thought that numeric integer constants are passed as int

if they fit into one? (see link above). How can it be that he works with long long

too?

Hell, it works even when alternating between int

and long long

. This is confusing 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); 
}

      

How can it be? I thought all my parameters were passed as int

... why can I randomly switch between int

and long long

without any problem? I'm really confused now ...

Thanks for any light on this!

+2


source to share


1 answer


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

Essentially, in the architecture being used, the compiler generates code that uses the full 64-bit case for every argument, including variable arguments. This ABI agrees with the architecture and has nothing to do with C per se; all programs, no matter how they are built, must follow the ABI in the architecture that it must execute.

If you are using Windows, x86-64 uses rcx

, rdx

, r8

and r9

for the first four (or integer index) arguments in this manner and for the rest of the stack. In Linux, BSD, Mac OS X and Solaris, x86-64 uses rdi

, rsi

, rdx

, rcx

, r8

and r9

for the first six (or integer index) argument, in that order, and for the rest of the stack.

You can check 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);
}

      

If you compile the above x86-64 assembly (for example gcc -Wall -O2 -march=x86-64 -mtune=generic -S

) on Linux, BSD, Solaris, or Mac OS (X or newer), you get approximately (AT&T syntax, source, target operand)



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

      

i.e. functions are identical and do not drag arguments onto the stack. Note that the jmp func

equivalent call func; ret

is simpler.

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

), you get something like

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

      

which shows that the x86 calling conventions in Linux / BSD / etc. They include transmitting variational argument on the stack, and that embodiment int

pushes a 32-bit constants to the stack ( pushl $x

pushing a 32-bit constant x

in the stack), and a variant long long

pushes the stack 64 bits.

Therefore, due to the underlying ABI of the operating system and architecture you are using, your variational function shows the "anomaly" that you observed. To see the behavior you'd expect from the C standard alone, you need to work around the underlying quirk ABI - for example, run your variadic functions with at least six arguments to occupy registers on x86-64 architectures so that the rest are truly variable arguments are passed onto the stack.

+5


source







All Articles