2

if I had a function receiving variable length argument

void print_arg_addr (int n, ...) 

I can use these three macro to parse the argument

va_start(ap,v)
va_arg(ap,t)
va_end(ap)  

In my understanding,

va_start lets ap point to second parameter,

va_arg move ap to next parameter,

va_end lets ap point to NULL.

so I use the snippet below to check my understanding, but it turns out that ap does not change, I expect ap will increment by 4 each time.

void print_arg_addr (int n, ...)
{
    int i;
    int val;
    va_list vl;
    va_start(vl,n);
    for (i=0;i<n;i++)
    {
        val=va_arg(vl,int);
        printf ("ap:%p , %d\n",vl,val);
    }
    va_end(vl);
    printf ("ap:%p \n",vl);
}

int main() 
{
    print_arg_addr(5,1,2,3,4,5);
}

output:

ap:0x7ffc62fb9890 , 1
ap:0x7ffc62fb9890 , 2
ap:0x7ffc62fb9890 , 3
ap:0x7ffc62fb9890 , 4
ap:0x7ffc62fb9890 , 5
ap:0x7ffc62fb9890 

Thank you!

user3094631
  • 425
  • 3
  • 13

1 Answers1

3

A va_list (like your vl) is some abstract data type that you are not allowed to pass to printf. Its implementation is private to your compiler (and processor architecture), and related to the ABI and calling conventions. Compile your code with all warnings and debug info: gcc -Wall -Wextra -g. You'll get warnings, and you have undefined behavior so you should be very scared.

In other words, consider va_list, va_start, va_end (and all stdarg(3) ...) as some magic provided by the compiler. That is why they are part of the C11 specification (read n1570) and often implemented as compiler builtins.

If you need to understand the internals of va_list and friends (but you should not need that), dive inside your compiler (and study your ABI). Since GCC is free software of millions of source code lines, you could spend many years studying it. In your case, I don't think it is worth the effort.

You might also look at the generated assembler code (a .s file), using gcc -O -S -fverbose-asm

Current calling conventions use processor registers. This is why understanding the details of variadic calls is complex. In the 1980s, arguments were pushed on the machine stack, and at that time va_start returned some pointer into the stack. Things are much more complex now, and you don't want to dive into that complexity.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Out of curiosity, what platform uses registers for vararg functions? All platforms I've seen (embedded mostly) just seem to give up and always use stack based calling conventions for vararg functions. – user694733 Nov 02 '17 at 07:30
  • 2
    @user694733 The calling conventions on SYSV amd64 are exactly the same for varargs and normal functions. Quick look at arm64 and powerpc shows the same. In fact, I'm quite sure that it's the only reasonable way to make an ABI if you still want to be able to support pre-ANSI C or old code in general. Ancient code can fully function while having the prototype `int printf();` and missing declarations are just a warning not an error in most compilers. For those things to keep working an ABI can't expect the caller to do anything smart and all varargs work is done in the callee. – Art Nov 02 '17 at 09:07