How can I write a function which takes a variable number of arguments

Q

How can I write a function which takes a variable number of arguments and passes them to some other function (which takes a variable number of arguments)?

✍: Guest

A

In general, you cannot. Ideally, you should provide a version of that other function which accepts a va_list pointer.
Suppose you want to write a faterror function which will print a fatal error message, then exit.
You might like to write it in terms of the error function
void faterror(const char *fmt, ...)
{
error(fmt, what goes here? );
exit(EXIT_FAILURE);
}

but it's not obvious how to hand faterror's arguments off to error.
Proceed as follows. First split up the existing error function to create a new verror which accepts not a variable argument list but a single va_list pointer. (Note that doing so is little extra work, because verror contains much of the code that used to be in error, and the new error becomes a simple wrapper around verror.)
#include <stdio.h>
#include <stdarg.h>

void verror(const char *fmt, va_list argp)
{
fprintf(stderr, "error: ");
vfprintf(stderr, fmt, argp);
fprintf(stderr, "\n");
}

void error(const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
verror(fmt, argp);
va_end(argp);
}

Now you can write faterror, and have it call verror, too:
#include <stdlib.h>
void faterror(const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
verror(fmt, argp);
va_end(argp);
exit(EXIT_FAILURE);
}

Note that the relation between error and verror is exactly that which holds between e.g. printf and vprintf. In fact, whenever you find yourself writing a varargs function, it's a good idea to write two versions of it: one (like verror) which accepts a va_list and does the work, and one (like the revised error) which is a simple wrapper. The only real restriction on this technique is that a function like verror can scan the arguments just once; there is no way for it to reinvoke va_start.
If you do not have the option of rewriting the lower-level function (error, in this example) to accept a va_list, such that you find yourself needing to pass the variable arguments that one function (e.g. faterror) receives on to another as actual arguments, no portable solution is possible. (The problem could perhaps be solved by resorting to machine-specific assembly language;
One approach that would not work would be something like
void faterror(const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
error(fmt, argp); /* WRONG */
va_end(argp);
exit(EXIT_FAILURE);
}

A va_list is not itself a variable argument list; it's really sort of a pointer to one. That is, a function which accepts a va_list is not itself varargs, nor vice versa.
Another kludge that is sometimes used, and which sometimes works even though it is grossly nonportable, is to use a lot of int arguments, hoping that there are enough of them and that they can somehow pass through pointer, floating-point, and other arguments as well:
void faterror(fmt, a1, a2, a3, a4, a5, a6)
char *fmt;
int a1, a2, a3, a4, a5, a6;
{
error(fmt, a1, a2, a3, a4, a5, a6); /* VERY WRONG */
exit(EXIT_FAILURE);
}

This example is presented only for the purpose of urging you not to use it; please don't try it just because you saw it here.

2015-06-05, 818👍, 0💬