Why I'm wrong about what sort of APIs C's stdargs allows

May 22, 2010

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.)

Written on 22 May 2010.
« An example of an API that you can't do with C stdargs
How I fixed Google's search results redesign »

Page tools: View Source.
Search:
Login: Password:

Last modified: Sat May 22 00:56:06 2010
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.