Columns


Standard C

The Header <stdarg. h>

P.J. Plauger


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.

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_list
which 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:

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.

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.

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:

#define _AUPBND  1
#define _ADNBND  1
I 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).