One of the things that blogging gives me is the chance to be very wrong in public. Yesterday , I claimed that C's stdargs didn't let you peel some arguments off the front of a
va_list
and then pass the shortened list to another function, such as
vprintf()
. Well, no, and now I'll tell you why I'm wrong.
I'm clearly wrong in practice on x86 Unix machines with gcc, as a simple test program easily demonstrated once a commentator raised doubts and I bothered to check. But I also believe that I'm wrong even in theory and that this sort of manipulation of
va_list
likely has to be supported by any spec-compliant C compiler. While this is not spelled out directly in the documentation I've read, I think that it arises by implication from things like the Single Unix Specification stdarg page .
(A disclaimer: I haven't read the ANSI/ISO C standard, so this may be clearly spelled out there.)
First, assume that you can write a standards-compliant C function that accepts a
va_list
argument and works directly with it, an equivalent of
vprintf()
or the like. The only way this function has to extract arguments from the
va_list
is with
va_arg()
, which it's allowed to use.
va_arg()
requires that you first call
va_start()
.
However, our
va_list
receiving function cannot call
va_start()
itself;
va_start()
must be invoked with the identifier of the rightmost parameter before the
...
in the varargs function definition , which exists only in the context of the caller of our function. So the caller must call
va_start()
before invoking our function, and in fact is the only function that can. And once you call
va_start()
, the behavior of
va_arg()
is quite well specified and contains no mention of the
va_list
being reset when you call a function; each time you call
va_arg()
, you advance the
va_list
to the next parameter (until you run out).
Hence I believe that the C standard almost certainly requires that if you call
va_arg()
and then pass the
va_list
to another function, that function sees the
va_list
just as you would and gets the same results from calling
va_arg()
that you would. Peeling arguments off your
va_list
and then calling a v* function with the remainder is perfectly spec-compliant behavior.
This still leaves the C stdarg stuff moderately constrained, but it's less constrained than I thought.
Sidebar: Why you have to reset
va_list
after function calls
va_list
is an opaque type that is effectively an iterator, and implementations are free to make it have internal state that is manipulated by
va_arg()
. Thus, when you call a function and pass it a
va_list
, that function may manipulate the internal state of your iterator and leave it in some random state, or simply at the end of the varargs parameters. So you have to reset it in order to be able to use it again yourself.
(This idea is achingly familiar to anyone who has ever passed iterators around in Python.)