25.3 Special Issues with Varargs Functions

When a function with a variable-length argument list is called, the variable arguments are passed using C's old ``default argument promotions.'' These say that types char and short int are automatically promoted to int, and type float is automatically promoted to double. Therefore, varargs functions will never receive arguments of type char, short int, or float. Furthermore, it's an error to ``pass'' the type names char, short int, or float as the second argument to the va_arg() macro. Finally, for vaguely related reasons, the last fixed argument (the one whose name is passed as the second argument to the va_start() macro) should not be of type char, short int, or float, either.

A frequently-asked question is, ``How can I determine how many arguments my function was actually called with?'' The answer, as discussed above, is that you (or your code) must figure it out somehow, generally by looking at the arguments themselves (or, in the case of printf, by using clues which are designed into the first, fixed arguments). There is no Standard way of asking the compiler or run-time system how many arguments were actually passed, or what their types are.

The macros va_start() and va_arg() are referred to as macros because they can't possibly be functions. va_start() initializes (that is, sets the value of) its first argument, and it uses its second argument not as a value but as a location. Even more unusually, va_arg() accepts a type name as its second argument, which no function in C, indeed no anything in C other than sizeof, can ever do. Finally, va_arg() has no one return type (as it would have to if it were a function); the type it ``returns'' is determined by its second argument.

The va_list type, whatever it is, is a mostly normal type. In particular, you can pass it on to other functions, and it is frequently quite useful to do so. For example, suppose that you want to write an error function, which will print nicely annotated error messages complete with filenames, line numbers, severity indicators, and the like. Furthermore, suppose that the rest of your program will find it useful, when calling this error function, to embed % sequences in the string to be printed, requesting that extra arguments be interpolated, just like printf. The obvious question is, will the error function have to duplicate all of printf's code for parsing format strings and formatting variable arguments (which isn't impossible, as we've seen), or can it somehow call on printf or a related function to do most of the work?

To be precise, here's the outline of the error function we'd like to write:

	extern char *filename;		/* current input file name */
	extern int lineno;		/* current line number */

	void error(char *msg, ...)
	{
		fprintf(stderr, "%s, line %d: error:", filename, lineno);
		fprintf(stderr, msg, what goes here? );
		fprintf(stderr, "\n");
	}
The tricky line is the second call to fprintf. We have the string, msg, we want to print, possibly containing % characters. How do we pass down to fprintf the extra arguments which our caller passed to us?

The answer is that we don't; there's no way to say ``call this function with the same arguments I got, however many of them there are.'' (The run-time system simply doesn't have enough information to do this sort of thing, which is why it can't tell you how many arguments you got called with, either.) However, there's a variant version of fprintf which is designed for just this sort of situation. The variant is called vfprintf (where the v stands for ``varargs''), and a call to it looks something like this:

	void error(char *msg, ...)
	{
		va_list argp;

		fprintf(stderr, "%s, line %d: error:", filename, lineno);
		va_start(argp, msg);
		vfprintf(stderr, msg, argp);
		va_end(argp);
		fprintf(stderr, "\n");
	}
We declare a local variable of type va_list and call va_start(), as before. However, all we do with our argp variable is pass it to vfprintf as its third argument. vfprintf then does all the work--if we could look inside it, it would look a lot like our version of printf above, except that vfprintf does not call va_start(), because its caller already has. Notice that vfprintf does not accept a variable number of arguments; it accepts exactly three arguments, the third of which is essentially a ``pointer'' to the extra arguments it will need.

There are also ``varargs'' versions of printf and sprintf, namely vprintf and vsprintf. These follow the same pattern, accepting a single last argument of type va_list in lieu of an actual variable-length argument list.

(Notice that the error function above also called va_end(). This makes sense, since error was the one who called va_start(). The above pattern works, but more complicated ones may not. For example, it's not guaranteed that you can pick a few arguments off of a va_list, pass the va_list to a subfunction to pick a few more off, and then pick the last ones off yourself. Also, there's no direct way to ``rewind'' a va_list, although it's permissible to call va_end() and then va_start() again, to start over again.)


Read sequentially: prev up top

This page by Steve Summit // Copyright 1996-1999 // mail feedback