25.2 Writing a ``varargs'' Function

In ANSI C, the head of a varargs function looks just like its prototype.
We will illustrate by writing our own,
stripped-down version of printf.
The bare outline of the function definition will look like this:

void myprintf(const char *fmt, ...)
{
}

printf's job, of course,
is
to print its format string
while
looking for % characters and treating them specially.
So the main loop of printf will look like this:

In this stripped-down version,
we won't worry about width and precision specifiers and other modifiers;
we'll always look at the very next character after the %
and assume that it's the primary format character.
Continuing to flesh out our outline, we get this:

(For clarity, we've rearranged
the former if/else statement slightly.
If the character we're looking at is not %,
we print it out and continue immediately
with the next iteration of the for loop.
This is a good example of the use of the continue statement.
Everything else in the body of the loop then
takes care of the case where we are looking at a %.)

Printing these various argument types out will be relatively straightforward.
The $64,000 question, of course, is how to fetch the actual arguments.
The answer involves some specialized macros defined for us
by the standard header <stdarg.h>.
The macros we will use are
va_list,
va_start(),
va_arg(),
and
va_end().
va_list is a special ``pointer'' type
which allows us to manipulate a variable-length argument list.
va_start() begins the processing of an argument list,
va_arg() fetches arguments from it, and
va_end() finishes processing.
(Therefore, va_list is a little bit like
the stdio FILE * type,
and va_start is a bit like fopen.)

Here is the final version of our myprintf function,
illustrating the fetching, formatting, and printing
of the various argument types.
(For simplicity--of presentation, if nothing else--the
formatting step is deferred to
a version of
the nonstandard but popular itoa function.)

This header file is required in any file
which uses the variable argument list (va_) macros.

va_list argp;

This line declares a variable, argp,
which we use while manipulating the variable-length argument list.
The type of the variable is va_list,
a special type defined for us by <stdarg.h>.

va_start(argp, fmt);

This line initializes argp
and initiates the processing of the argument list.
The second argument to va_start() is simply
the name of the
function's
last fixed argument.
va_start() uses this
to figure out
where the variable arguments begin.

i = va_arg(argp, int);

And here's the heart of the matter.
va_arg() fetches the next argument from the argument list.
The second argument to va_arg() is
the type of the argument we expect.
Notice carefully that we must supply this argument,
which implies that we must somehow know
what type of argument to expect next.
The variable-length argument list machinery does not know.
In this case, we know what the type of the next argument
should be
because it's
supposed to match
the format character we're processing.
We can see,
then,
why such havoc results
when printf's arguments
do not match its format string:
printf tells the va_arg machinery
to grab an argument of one type,
with the type determined by one of the format specifiers,
but since the va_arg machinery doesn't know
what the actual argument type is,
there's no way for it to do any automatic conversion.
If the actual argument
has the right type for the va_arg call which grabs it
(as
of course
it's supposed to),
it works,
otherwise it doesn't.

(You may have noticed that we fetched the character
to print for %c
as an int, not a char.
That's deliberate, and is explained in the next section.)

s = va_arg(argp, char *);

Here's another invocation of va_arg(),
this time fetching a string, represented as a character pointer,
or char *.

va_end(argp);

Finally, when we're all finished processing the argument list,
we call va_end(), which performs any necessary cleanup.