Why I'm wrong about what sort of APIs C's stdargs allows
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.)
|
|