49

Here I found an example of how varargs can be used in C.

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

I can understand this example only to some extent.

  1. It is not clear to me why we use va_start(ap, count);. As far as I understand, in this way we set the iterator to its first element. But why it is not set to the beginning by default?

  2. It is not clear to me why we need to give count as an argument. Can't C automatically determine the number of the arguments?

  3. It is not clear to me why we use va_end(ap). What does it change? Does it set the iterator to the end of the list? But is it not set to the end of the list by the loop? Moreover, why do we need it? We do not use ap anymore; why do we want to change it?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Roman
  • 124,451
  • 167
  • 349
  • 456

4 Answers4

52

Remember that arguments are passed on the stack. The va_start function contains the "magic" code to initialize the va_list with the correct stack pointer. It must be passed the last named argument in the function declaration or it will not work.

What va_arg does is use this saved stack pointer, and extract the correct amount of bytes for the type provided, and then modify ap so it points to the next argument on the stack.


In reality these functions (va_start, va_arg and va_end) are not actually functions, but implemented as preprocessor macros. The actual implementation also depends on the compiler, as different compilers can have different layout of the stack and how it pushes arguments on the stack.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • How about passing variable arguments using an array of pointers to char? Something like `char* argv[]` that's used to pass variable arguments to the program. – John Strood Sep 22 '16 at 04:18
  • 1
    @Djack Of course it works, but then the function is no longer a "variable argument" function the way the language sees it. You also need to convert everything to strings, handle dynamic allocation for the array and the strings, etc. All in al much more work. – Some programmer dude Sep 22 '16 at 07:52
  • 3
    It might be worthy to note that in AMD64 ABI, most of the arguments are passed in registers. – Janman Sep 24 '16 at 17:42
11

But why it is not set to the beginning by default?

Maybe because of historical reasons from when compilers weren't smart enough. Maybe because you might have a varargs function prototype which doesn't actually care about the varargs and setting up varargs happens to be expensive on that particular system. Maybe because of more complex operations where you do va_copy or maybe you want to restart working with the arguments multiple times and call va_start multiple times.

The short version is: because the language standard says so.

Second, it is not clear to me why we need to give count as an argument. Can't C++ automatically determine the number of the arguments?

That's not what all that count is. It is the last named argument to the function. va_start needs it to figure out where the varargs are. Most likely this is for historical reasons on old compilers. I can't see why it couldn't be implemented differently today.

As the second part of your question: no, the compiler doesn't know how many arguments were sent to the function. It might not even be in the same compilation unit or even the same program and the compiler doesn't know how the function will be called. Imagine a library with a varargs function like printf. When you compile your libc the compiler doesn't know when and how programs will call printf. On most ABIs (ABI is the conventions for how functions are called, how arguments are passed, etc) there is no way to find out how many arguments a function call got. It's wasteful to include that information in a function call and it's almost never needed. So you need to have a way to tell the varargs function how many arguments it got. Accessing va_arg beyond the number of arguments that were actually passed is undefined behavior.

Then it is not clear to me why do we use va_end(ap). What does it change?

On most architectures va_end doesn't do anything relevant. But there are some architectures with complex argument passing semantics and va_start could even potentially malloc memory then you'd need va_end to free that memory.

The short version here is also: because the language standard says so.

Art
  • 19,807
  • 1
  • 34
  • 60
  • 2
    This has absolutely nothing to do with "compilers not being smart enough". The nature of a variadic function is such that the number of arguments passed is determined at runtime but the compiler operates at compile time. It is thus impossible for any compiler, no matter how smart to know the number of arguments that will be passed to a variadic function. The only way to do this at compile time would be to reserve an automatic "invisible" parameter for the argument count, and let the compiler always initialise it, but the designers of C chose not to implement variadic functions this way. – trijezdci Feb 02 '16 at 10:37
  • 3
    @trijezdci Eh? What does the number of arguments have to do with which vararg argument will be the first? I see no reason for the compiler to not be able to figure out that in `foo(int a, ...)` the varargs start after `a`. Which is what the first question was about and is perfectly well known at compile time. The count of arguments not being known at compile time I actually mention in the answer, so I don't understand why you felt the need to repeat it. – Art Feb 02 '16 at 11:42
  • The compiler could count the args at compile time, but the function itself executes at runtime and the only way to pass that count from compile time to runtime would be to pass it as an extra argument, possibly hidden, but nevertheless as an extra argument. It has thus nothing to do with the "smartness" of the compiler but it has all to do with the parameter passing convention. C simply does not have any such convention by which the argument count is automatically passed as an extra parameter, instead, C leaves this up to the programmer to do manually. – trijezdci Feb 02 '16 at 13:17
  • The very same applies of course to the position where the variadic list starts. This is information that is available at compile time only. Lacking any parameter passing convention whereby this information is passed as an additional parameter, the information is no longer available at runtime either. Since C lacks such conventions, the VA macros are required to add the information back in manually for use at runtime. – trijezdci Feb 02 '16 at 13:24
  • 3
    @trijezdci You're talking about the argument count again which is completely irrelevant. As to your second comment, where the variadic list starts it is perfectly clear at compile time and there's no way to start it at any other point. With `foo(int a, ...)` it is perfectly clear that the variadic list starts after `a` always. The C standard says "The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...).". Every compiler knows which parameter is the rightmost, "just before the ...". – Art Feb 02 '16 at 13:32
  • The point is that the information is available at compile time but not at runtime. C could be extended with a convention to pass the information in form of extra parameters to a variadic function so it will be available at runtime. Without such a convention, the information will get lost. At present, C does not have such a convention. – trijezdci Feb 02 '16 at 13:39
  • For example, C syntax could be extended to allow two extra formal parameters after ..., the first for the argument count, the second for the list element type, e.g. `void foo ( ... int counter, int arglist );`, any call of the form `foo(42, 43, 44)` would then be compiled as `foo(3, 42, 43, 44)`, where the compiler would thereby pass the argument count automatically before the list. Further, the list argument could syntactically be treated as an array, thus `arglist[i]` within the function body would automatically be translated to a reference on the stack for the referenced argument. – trijezdci Feb 02 '16 at 13:46
  • Unfortunately though, C does not have any such convention. It is not a matter of the state of the art of compiler technology but simply a matter of design philosophy and design choice. C's philosophy is to let programmers do everything manually, with few if any automatisms. – trijezdci Feb 02 '16 at 13:49
  • 3
    What are you on about? Read what the actual question was. "why is va_start needed?" was the question (paraphrased), I listed a bunch of possible reasons, including that old compilers weren't smart enough to know that there was varargs in the function at all (gcc around 2.6 or so didn't know) and also had to be told where it started (because it didn't keep track of that information). It has nothing to do with runtime at all. – Art Feb 02 '16 at 13:54
  • the question was about why the compiler doesn't do this automatically and the suggestion was that it was perhaps because the compiler technology wasn't up to the task. I am saying, it is a design choice, not a limit imposed by the state of the art of compiler technology, neither now, nor at the time C was first conceived. – trijezdci Feb 02 '16 at 13:56
7

va_start initalises the list of variable arguments. You always pass the last named function argument as the second parameter. It is because you need to provide information about location in the stack, where variable arguments begin, since arguments are pushed on the stack and compiler cannot know where there's a beginning of variable argument list (there's no differentiation).

As to va_end, it is used to free resources allocated for the variable argument list during the va_start call.

W.B.
  • 5,445
  • 19
  • 29
3

It's C macroses. va_start sets internal pointer to address of first element. va_end cleanup va_list. If there is va_start in code and there is no va_end - it's UB.

The restrictions that ISO C places on the second parameter to the va_start() macro in header are different in this International Standard. The parameter parmN is the identifier of the rightmost parameter in the variable parameter list of the function definition (the one just before the ...). If the parameter parmN is declared with a function, array, or reference type, or with a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

ForEveR
  • 55,233
  • 2
  • 119
  • 133