66

va_end - Macro to reset arg_ptr.

After accessing a variable argument list, the arg_ptr pointer is usually reset with va_end(). I understand that it is required if you want to re-iterate the list, but is it really needed if you aren't going to? Is it just good practice, like the rule "always have a default: in your switch"?

jscs
  • 63,694
  • 13
  • 151
  • 195
Yarik
  • 1,172
  • 2
  • 9
  • 17
  • 6
    It's a really good question. I wish someone would answer it by describing an architecture where va_end is not a no-op. – erikkallen Feb 25 '09 at 18:20
  • 1
    FYI: MSVS2008 - #define _crt_va_end(ap) ( ap = (va_list)0 ) – Yarik Feb 25 '09 at 18:30
  • @erikkallen: Do a google search for "define va_end" and you will find quite some unusual definitions that may or may not be essentially a no-op. – PlasmaHH Apr 03 '12 at 21:02

3 Answers3

57

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.

Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
greyfade
  • 24,948
  • 7
  • 64
  • 80
  • Does it mean that the pointer is 'global' and when the function is called a second time without resetting the pointer, the stack will become corrupted? – Yarik Feb 25 '09 at 18:07
  • 3
    It could become corrupted because *you don't know what va_start() is doing.* It could be doing anything. And it needs to be cleaned up. Therefore, when you call va_start(), you *MUST* match it with va_end(). – greyfade Feb 25 '09 at 18:11
  • 13
    No one is really explaining the nitty gritty of a va_end() implementation, which is, I think, what the question was getting at. – Adam Feb 25 '09 at 18:16
  • 2
    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. You don't know. That's my point. – greyfade Feb 25 '09 at 18:19
  • 4
    indeed. some ABIs pass first few args in registers, and remaining on the stack. a va_arg there may be more complicated. some are very easy.. you never know – Johannes Schaub - litb Feb 25 '09 at 18:21
  • 1
    Sure. You can look up how a given implementation does varargs, which may be interesting, but in writing portable code you treat them as atomic operations. Remember that sticking to the standard is safest, and in this case there's barely any penalty in doing so. – David Thornley Feb 25 '09 at 19:04
  • 7
    @Adam: it obviously depends on the platform. For many machines, it is a no-op. That means you can ignore the standard stricture about MUST use va_end() and you will mostly get away with it - but it is very foolish coding. Very foolish! – Jonathan Leffler Feb 25 '09 at 22:14
  • 17
    The C Language Rationale mentions that some implementations allocate memory in `va_start`. The `va_end` would therefore free that memory. "In many implementations, [`va_end`] is a do-nothing operation; but those implementations that need it probably need it badly." – Raymond Chen Apr 03 '12 at 21:03
16

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.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 4
    To cliarfy this; nothing is special to Linux x86-64. `va_copy` is required if you want to iterate twice over a list when you only have the `va_list` variable available. (e.g. inside a function taking `va_list` as argument). You can always call `va_start` and `va_end` as much as you want. – M.M Aug 27 '15 at 21:20
  • @MattMcNabb On x86-64 `va_list` maintains the state of traversal. On x86 it does not. – Maxim Egorushkin Aug 28 '15 at 14:54
  • I don't see how this answers the question. It only talks about `va_copy`, not `va_end`. Also, `va_list` maintains the state of traversal on all platforms, otherwise `va_next` would be impossible (and hence `vfprintf`, etc). – melpomene Nov 07 '17 at 15:21
  • @jrh Good idea. Done. – Maxim Egorushkin Dec 04 '17 at 17:03
14

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.

James Curran
  • 101,701
  • 37
  • 181
  • 258