P.J. Plauger is senior editor of The C Users Journal. He is secretary of the ANSI C standards committee, X3J11, and convenor of the ISO C standards committee, WG14. His latest book is The Standard C Library, published by Prentice-Hall. You can reach him at PJP@wsa.oz; or uunet!munnari!wsa.oz!pjp.
One of the great powers of C is that it lets you define functions that accept a variable argument list. Other languages have such creatures, to be sure, but the number of such functions is fixed. All are special functions built into the language. You cannot define additional ones.
To access the additional arguments in a variable argument list, you need the macros defined in <stdarg.h>. They let you walk along the list of extra arguments from beginning to end as often as you like. You must know the type of each argument before you encounter it. But you need not know the particulars of any given call before it occurs. You can determine the number and types of arguments from one of the fixed arguments, for example, such as a format string.
The header <stdarg.h> is an invention of committee X3J11. It is based heavily on the header <varargs.h> that was developed as part of the Berkeley enhancement to the UNIX operating system. <varargs.h> was one of several contemporaneous attempts at isolating implementation dependencies in walking variable argument lists. It was also one of the most widely known. The idea was to make a common operation more portable by hiding differences inside macros.
In the early days, no such hiding was necessary. C was a language for the PDP-11, period. Everyone knew how Dennis Ritchie's compiler laid out an argument list in memory. Walking from argument to argument was a simple exercise in pointer arithmetic. It helped that pointers were the same size as ints and that structures were not yet permitted as arguments. That meant that an argument could be treated as either an int, a long, or a double. Since double has the same storage alignment as int on the PDP-11, there was no worry about holes left in the argument list to ensure proper storage alignment.
The advent of structure arguments and pointers of varied sizes made life messier. Even if you had no interest in writing portable code, you still wanted it to be readable. That increased the demand for notation that could hide the messy details of walking a variable argument list.
Then along came implementations of C designed to work with older programming languages such as FORTRAN. It was sometimes necessary for such implementations to use a calling sequence that differed dramatically from that used on the PDP-11. Argument lists sometimes grew downward in memory instead of upward. Some involved intermediate pointers to the actual argument values. Hiding the details of accessing an argument moved from being a convenience to a necessity.
Committee X3J11 felt obliged to change the Berkeley macros in several small ways. For this reason, the C Standard specifies the standard header with a new name. <stdarg.h> differs just enough from <varargs.h> to confuse programs (and programmers) that use the older header. The committee debated ways to make the capabilities of <stdarg.h> more a part of the language. In the end, however, the committee elected to leave as macros the mechanisms for walking a variable argument list.
What X3J11 did instead was endeavor to generalize the macros as much as possible. The idea was to define the macros in such a way that all known implementations of C could conform without major change. Some implementations had to alter their translators to provide critical information or operations. Most, however, can support <stdarg.h> with no help from the translator proper.
Some of the restrictions imposed on the macros defined in <stdarg.h> seem unnecessarily severe. For some implementations, they are. Each was introduced, however, to meet the needs of at least one serious C implementation. For example:
All in all, however, the macros defined in <stdarg.h> work well enough. And they offer a serivce which is uniquely powerful among modern programming languages.
- A function must declare at least one fixed argument. The macro va_start refers to the last of the fixed arguments so that it can locate the variable argument list.
- You cannot specify argument types in va_arg that "widen" in the absence of a function prototype. You must write double, for example, instead of float. The macros cannot replicate the rules for altering argument types that apply to a variable argument list.
- You can write only certain argument types in va_arg. Many macro implementations generate a related pointer type by textually appending a *. The rules for writing types in C are notoriously introverted and much too twisty for such a simple recipe to work right all the time.
- A function must execute va_end before it returns to its caller. Some implementations must tidy up control information before a return can occur.
What The C Standard Says
7.8 Variable arguments <stdarg. h>
The header <stdarg.h> declares a type and defines three macros, for advancing through a list of arguments whose number and types are not known to the called function when it is translated.A function may be called with a variable number of arguments of varying types. As described in 7.7.1, its parameter list contains one or more parameters. The rightmost parameter plays a special role in the access mechanism, and will be designated parmN in this description.
The type declared is
va_listwhich is a type suitable for holding information needed by the macros va_start, va_arg, and va_end. If access to the varying arguments is desired, the called function shall declare an object (referred to as ap in this subclause) having type va_list. The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.
7.8.1 Variable argument list access macros
The va_start and va_arg macros described in this subclause shall be implemented as macros, not as actual functions. It is unspecified whether va_end is a macro or an identifier declared with external linkage. If a macro definition is suppressed in order to access an actual function, or a program defines an external identifier with the name va_end, the behavior is undefined. The va_start and va_end macros shall be invoked in the function accepting a varying number of arguments, if access to the varying arguments is desired.
7.8.1.1 The va_start macro
Synopsis
#include <stdarg.h> void va_start(va_list ap, parmN);Description
The va_start macro shall be invoked before any access to the unnamed arguments.The va_start macro initializes ap for subsequent use by va_arg and va_end.
The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the, ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.
Returns
The va_start macro returns no value.
7.8.1.2 The va_arg macro
Synopsis
#include <stdarg.h> type va_arg(va_list ap, type);Description
The va_arg macro expands to an expression that has the type and value of the next argument in the call. The parameter ap shall be the same as the va_list ap initialized by va_start. Each invocation of va_arg modifies ap so that the values of successive arguments are returned in turn. The parameter type is a type name specified such that the type of a pointer to an object that has the specified type can be obtained simply by postfixing a * to type. If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined.
Returns
The first invocation of the va_arg macro after that of the va_start macro returns the value of the argument after that specified by parmN. Successive invocations return the values of the remaining arguments in succession.
7.8.1.3 The va_end macro
Synopsis
#include <stdarg.h> void va_end(va_list ap);Description
The va_end macro facilitates a normal return from the function whose variable argument list was referred to by the expansion of va_start that initialized the va_list ap. The va_end macro may modify ap so that it is no longer usable (without an intervening invocation of va_start). If there is no corresponding invocation of the va_start macro, or if the va_end macro is not invoked before the return, the behavior is undefined.
Returns
The va_end macro returns no value.
Example
The function f1 gathers into an array a list of arguments that are pointers to strings (but not more than MAXARGS arguments), then passes the array as a single argument to function f2. The number of pointers is specified by the first argument to f1.
#include <stdarg.h> #define MAXARGS 31 void f1(int n_ptrs, ....) { va_list ap; char *array[MAXARGS]; int ptr_no = 0; if (n_ptrs > MAXARGS) n_ptrs = MAXARGS; va_start(ap, n_ptrs); while (ptr_no < n_ptrs) array[ptr_no++] = va_arg(ap, char *); va_end (ap); f2(n_ptrs, array); }Each call to f1 shall have visible the definition of the function or a declaration such as
void f1(int, ....);Using <stdarg. h>
You use the macros defined in <stdarg.h> to walk a variable argument list. The macros must accommodate the needs of diverse implementations. Hence they come with a number of caveats:
If all that sounds too negative, consider a positive example instead. Here is a function that generalizes the function fputs, declared in <stdio.h>. That function writes a single null-terminated string to an output stream that you designate, as in:
- You must declare a function explicitly as having a variable argument list. (Call it f.) That means its argument list must end in ellipsis (, ...), both in its definition and any declarations. Moreover, all calls to the function must be in scope of a function prototype that declares the function this way.
- You must declare the function with at least one fixed argument. The last of these fixed arguments is conventionally referred to as parmN.
- You must declare a data object of type va_list, conventionally called ap. The data object must, of course, be visible within the function.
- You must execute va_start(ap, parmN) within f. You must not execute va_list or va_end until you do so.
- You can then execute va_arg(ap,T) in the function or in any of the functions that it calls. You must specify the proper types for each of the arguments, of course, and in the order that they appear in the function call. Note that va_arg is an rvalue macro. You cannot use the macro invocation as an 1value to alter the value stored in the argument data object.
- You must not write a type T that widens when passed as an argument. Replace float with double. Replace char, signed char, unsigned char, short,and unsigned short with either int or unsigned int. Use unsigned int for an unsigned short that is the same size as int. Rarer still, use unsigned int for a character type that represents no negative values and is the same size as int.
- You must write only a type T that can be converted to a pointer type by appending a *. For example, the type designators int and char * are valid. The type designator char (*) [5] is not. As a general rule, be wary of type designators that contain parentheses or brackets.
- You must execute va_end within f if you earlier executed va_start. Once you execute va_end you must not again execute va_arg unless you first execute va_start to initiate a rescan. In that case, you must execute va_end again before the function returns.
fputs("this is a test", stdout);This function, called va_fputs, writes an arbitrary number of strings to a given stream, as in:
va_fputs(stdout, "this is", "a test", NULL);In this example, both functions should produce the same output to the stream stdout.You can write va_fputs as:
include <stdarg.h> #include <stdout.h> int va_fputs(FILE *str, ...) { /* write zero or more strings */ char *s; va_list va_list ap; va_start(ap, str); while (s = va_arg(ap, char *)) if (fputs(str, s) < 0) return (EOF); /* write error */ va_end (ap); return (0); /* all writes successful */ }You can follow this pattern to process a wide range of variable argument lists. You can even process the variable argument list in a separate function. Be sure to execute va_start before you call the function. Then execute va_end when the function returns.If you want to rescan a variable argument list you have to be a bit more careful. Execute va_start to initiate each rescan, of course. Execute va_end before the function returns, and only if you execute va_start at least once. I recommend an even safer discipline execute va_start and va_end within the same loop. That way, you are more certain to execute va_end only when you should.
Many implementations have no need for va_end. The macro expands to code that does nothing. That means that any errors in using this macro become time bombs that may not go off for years. They get more expensive to find and fix with each passing year. Take pains to eliminate the bugs up front.
Another danger lurks in calling a function with the argument ap (the data object of type va_list). In some implementations, it may be an array type. That means that the function parameter actually becomes a pointer to the first element of the va_list array. When the called function executes va_arg, the data object changes in the calling function (called f above).
In other implementations, va_list is not an array type. That means that the argument ap passes by value as it appears to do. When the called function executes va_arg, the data object in the calling function f does not change.
If you process all arguments in the called function, the difference doesn't matter. If you execute va_arg in different function invocations with the "same" ap, however, it can matter. In fact, you get in trouble if your code requires that the va_list data object be shared or if it requires that the data object not be shared.
You can ensure the behavior that you need:
These two recipes will work regardless of the type defined for va_list.
- If the va_list data object must be shared, write the argument as &ap. Declare the corresponding parameter as va_list *pap. Within the function, execute va_arg(*pap, T) to access each argument in the variable argument list.
- If the va_list data object must not be shared, write the argument as ap. Declare the corresponding parameter as va_list xap. Within the function, declare a data object as va_list ap and execute memcpy(ap, xap, sizeof (va_list)). (memcpy is declared in <string.h>.) Execute va_arg(ap, T) to access each argument in the variable argument list.
Implementing <stdarg.h>
Listing 1 shows the file stdarg.h. It is the only code needed to implement <stdarg.h>. That's assuming that it can be made to work with a given implementation of Standard C.The approach assumes that:
These assumptions hold for many implementations of Standard C.
- A variable argument list occupies a contiguous array of characters in memory.
- Successive arguments occupy successively higher elements of the character array.
- The space occupied by an argument begins on a storage boundary that is some multiple of 2N bytes.
- The size of the space is the smallest multiple of 2N bytes that can represent the argument.
- Any "hole" left in the space is always at the beginning or always at the end of the argument data object.
As usual, I use the internal header <yvals.h> to define macros that describe variations among different systems. For the header <stdarg. h>, two parameters are relevant:
A simple example is the Borland Turbo C++ compiler. For that implementation, the header <yvals.h> contains the definitions:
- _AUPBND is a mask that determines the storage boundary enforced within the variable argument list. Its value is 2N-1.
- _ADNBND is a mask that determines whether the hole is at the beginning or at the end of an argument data object. Its value is 2N-1 if the hole is at the end, otherwise it is zero.
#define _AUPBND 1 #define _ADNBND 1I discovered the need for specifying a hole before an argument with the GNU C compiler for the Sun UNIX workstation. For that system, _AUPBND has the value 3, but _ADNBND is zero.Perhaps now you can understand the trickery involved in writing starg.h. The type va_list is just a pointer to char. Such a data object holds a pointer to the start of the next argument space.
The macro va_start skips past the named argument, which should be the last of the fixed arguments. It uses the internal macro _Bnd to round up the size of its argument to a multiple of 2N bytes.
The macro va_arg is the trickiest of the lot. It begins by incrementing the contents of the va_list data object to point to the start of the next argument space. Then it backs up to point to the beginning of the current argument. Then it type casts that pointer value to be a pointer to the specified type. Its last act is to dereference the pointer to access the value stored in the data object. (In this implementation, va_arg is an lvalue. Don't count on that being true of others.)
The macro va_end has nothing to do in this implementation. It expands to the place-holder expression (void)0.
Conclusion
Perhaps <stdarg.h> could have been made cleaner. Perhaps not. In any event, it appears to be clean enough, if you don't try to get too clever with the macros it defines. And it does offer a service unique among programming languages.This article is excerpted from P.J. Plauger, The Standard C Library, (Englewood Cliffs, N.J.: Prentice-Hall 1992).