What exactly is va_end for? Is it always necessary to call it?

Solution 1:

va_end is used to do cleanup. You don't want to smash the stack, do you?

From man va_start:

va_end()

Each invocation of va_start() must be matched by a corresponding invocation of va_end() in the same function. After the call va_end(ap) the variable ap is undefined. Multiple traversals of the list, each bracketed by va_start() and va_end() are possible. va_end() may be a macro or a function.

Note the presence of the word must.

The stack could become corrupted because you don't know what va_start() is doing. The va_* macros are meant to be treated as black boxes. Every compiler on every platform can do whatever it wants there. It may do nothing, or it may do a lot.

Some ABIs pass the first few args in registers, and the remainder on the stack. A va_arg() there may be more complicated. You can look up how a given implementation does varargs, which may be interesting, but in writing portable code you should treat them as opaque operations.

Solution 2:

On Linux x86-64 only one traversal can be done over a va_list variable. To do more traversals it has to be copied using va_copy first. man va_copy explains the details:

va_copy()

An obvious implementation would have a va_list be a pointer to the stack frame of the variadic function. In such a setup (by far the most common) there seems nothing against an assignment

   va_list aq = ap;

Unfortunately, there are also systems that make it an array of pointers (of length 1), and there one needs

   va_list aq;
   *aq = *ap;

Finally, on systems where arguments are passed in registers, it may be necessary for va_start() to allocate memory, store the arguments there, and also an indication of which argument is next, so that va_arg() can step through the list. Now va_end() can free the allocated memory again. To accommodate this situation, C99 adds a macro va_copy(), so that the above assignment can be replaced by

   va_list aq;
   va_copy(aq, ap);
   ...
   va_end(aq);

Each invocation of va_copy() must be matched by a corresponding invoca‐ tion of va_end() in the same function. Some systems that do not supply va_copy() have __va_copy instead, since that was the name used in the draft proposal.

Solution 3:

In the common "parameters passed on the stack" implementation, I believe va_end() is usually nothing/empty/null. However, on platforms which have less traditional schemes, it becomes necessary. It's a "good practice" to include it to remain platform neutral.