15. Variable-Length Argument Lists

comp.lang.c FAQ list · Question 15.1

Q: I heard that you have to #include <stdio.h> before calling printf. Why?


A: So that a proper prototype for printf will be in scope.

A compiler may use a different calling sequence for functions which accept variable-length argument lists. (It might do so if calls using variable-length argument lists were less efficient than those using fixed-length.) Therefore, a prototype (indicating, using the ellipsis notation ``...'', that the argument list is of variable length) must be in scope whenever a varargs function is called, so that the compiler knows to use the varargs calling mechanism.

References: ISO Sec. 6.3.2.2, Sec. 7.1.7
Rationale Sec. 3.3.2.2, Sec. 4.1.6
H&S Sec. 9.2.4 pp. 268-9, Sec. 9.6 pp. 275-6




comp.lang.c FAQ list · Question 15.2

Q: How can %f be used for both float and double arguments in printf? Aren't they different types?


A: In the variable-length part of a variable-length argument list, the ``default argument promotions'' apply: types char and short int are promoted to int, and float is promoted to double. (These are the same promotions that apply to function calls without a prototype in scope, also known as ``old style'' function calls; see question 11.3.) Therefore, printf's %f format always sees a double. (Similarly, %c always sees an int, as does %hd.) See also questions 12.9 and 12.13.

References: ISO Sec. 6.3.2.2
H&S Sec. 6.3.5 p. 177, Sec. 9.4 pp. 272-3




comp.lang.c FAQ list · Question 15.3

Q: I had a frustrating problem which turned out to be caused by the line

	printf("%d", n);
where n was actually a long int. I thought that ANSI function prototypes were supposed to guard against argument type mismatches like this.


A: When a function accepts a variable number of arguments, its prototype does not (and cannot) provide any information about the number and types of those variable arguments. Therefore, the usual protections do not apply in the variable-length part of variable-length argument lists: the compiler cannot perform implicit conversions or (in general) warn about mismatches. The programmer must make sure that arguments match, or must manually insert explicit casts.

In the case of printf-like functions, some compilers (including gcc) and some versions of lint are able to check the actual arguments against the format string, as long as the format string is an immediate string literal.

See also questions 5.2, 11.3, 12.9, and 15.2.




comp.lang.c FAQ list · Question 15.4

Q: How can I write a function that takes a variable number of arguments?


A: Use the facilities of the <stdarg.h> header.

Here is a function which concatenates an arbitrary number of strings into malloc'ed memory:

#include <stdlib.h>		/* for malloc, NULL, size_t */
#include <stdarg.h>		/* for va_ stuff */
#include <string.h>		/* for strcat et al. */

char *vstrcat(const char *first, ...)
{
	size_t len;
	char *retbuf;
	va_list argp;
	char *p;

	if(first == NULL)
		return NULL;

	len = strlen(first);

	va_start(argp, first);

	while((p = va_arg(argp, char *)) != NULL)
		len += strlen(p);

	va_end(argp);

	retbuf = malloc(len + 1);	/* +1 for trailing \0 */

	if(retbuf == NULL)
		return NULL;		/* error */

	(void)strcpy(retbuf, first);

	va_start(argp, first);		/* restart; 2nd scan */

	while((p = va_arg(argp, char *)) != NULL)
		(void)strcat(retbuf, p);

	va_end(argp);

	return retbuf;
}
(Note that a second call to va_start is needed to re-start the scan when the argument list is processed a second time. Note the calls to va_end: they're important for portability, even if they don't seem to do anything.)

A call to vstrcat looks something like

	char *str = vstrcat("Hello, ", "world!", (char *)NULL);
Note the cast on the last argument; see questions 5.2 and 15.3. (Also note that the caller must free the returned, malloc'ed storage.)

vstrcat accepts a variable number of arguments, all of type char *. Here is an example which accepts a variable number of arguments of different types; it is a stripped-down version of the familiar printf function. Note that each invocation of va_arg() specifies the type of the argument being retrieved from the argument list.

(The miniprintf function here uses baseconv from question 20.10 to format numbers. It is significantly imperfect in that it will not usually be able to print the smallest integer, INT_MIN, properly.)

#include <stdio.h>
#include <stdarg.h>
#ifdef MAIN

void miniprintf(const char *, ...);

main()
{
	miniprintf("Hello, world!\n");
	miniprintf("%c %d %s\n", '1', 2, "three");
	miniprintf("%o %d %x\n", 10, 10, 10);
	miniprintf("%u\n", 0xffff);
	return 0;
}

#endif

extern char *baseconv(unsigned int, int);

void
miniprintf(const char *fmt, ...)
{
	const char *p;
	int i;
	unsigned u;
	char *s;
	va_list argp;

	va_start(argp, fmt);

	for(p = fmt; *p != '\0'; p++) {
		if(*p != '%') {
			putchar(*p);
			continue;
		}

		switch(*++p) {
		case 'c':
			i = va_arg(argp, int);
			/* *not* va_arg(argp, char); see Q 15.10 */
			putchar(i);
			break;

		case 'd':
			i = va_arg(argp, int);
			if(i < 0) {
				/* XXX won't handle INT_MIN */
				i = -i;
				putchar('-');
			}
			fputs(baseconv(i, 10), stdout);
			break;

		case 'o':
			u = va_arg(argp, unsigned int);
			fputs(baseconv(u, 8), stdout);
			break;

		case 's':
			s = va_arg(argp, char *);
			fputs(s, stdout);
			break;

		case 'u':
			u = va_arg(argp, unsigned int);
			fputs(baseconv(u, 10), stdout);
			break;

		case 'x':
			u = va_arg(argp, unsigned int);
			fputs(baseconv(u, 16), stdout);
			break;

		case '%':
			putchar('%');
			break;
		}
	}

	va_end(argp);
}

See also question 15.7.

References: K&R2 Sec. 7.3 p. 155, Sec. B7 p. 254
ISO Sec. 7.8
Rationale Sec. 4.8
H&S Sec. 11.4 pp. 296-9
CT&P Sec. A.3 pp. 139-141
PCS Sec. 11 pp. 184-5, Sec. 13 p. 242




comp.lang.c FAQ list · Question 15.5

Q: How can I write a function that takes a format string and a variable number of arguments, like printf, and passes them to printf to do most of the work?


A: Use vprintf, vfprintf, or vsprintf. These routines are like their counterparts printf, fprintf, and sprintf, except that instead of a variable-length argument list, they accept a single va_list pointer.

As an example, here is an error function which prints an error message, preceded by the string ``error: '' and terminated with a newline:

#include <stdio.h>
#include <stdarg.h>

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

See also question 15.7.

References: K&R2 Sec. 8.3 p. 174, Sec. B1.2 p. 245
ISO Secs. 7.9.6.7,7.9.6.8,7.9.6.9
H&S Sec. 15.12 pp. 379-80
PCS Sec. 11 pp. 186-7




comp.lang.c FAQ list · Question 15.6

Q: How can I write a function analogous to scanf, i.e. that accepts similar arguments, and calls scanf to do most of the work?


A: C99 (but not any earlier C Standard) supports vscanf, vfscanf, and vsscanf.

References: C9X Secs. 7.3.6.12-14




comp.lang.c FAQ list · Question 15.7

Q: I have a pre-ANSI compiler, without <stdarg.h>. What can I do?


A: There's an older header, <varargs.h>, which offers about the same functionality.

Here is the vstrcat function from question 15.4, rewritten to use <varargs.h>:

#include <stdio.h>
#include <varargs.h>
#include <string.h>

extern char *malloc();

char *vstrcat(va_alist)
va_dcl		/* no semicolon */
{
	int len = 0;
	char *retbuf;
	va_list argp;
	char *p;

	va_start(argp);

	while((p = va_arg(argp, char *)) != NULL)	/* includes first */
		len += strlen(p);

	va_end(argp);

	retbuf = malloc(len + 1);		/* +1 for trailing \0 */

	if(retbuf == NULL)
		return NULL;			/* error */

	retbuf[0] = '\0';

	va_start(argp);			/* restart for second scan */

	while((p = va_arg(argp, char *)) != NULL)	/* includes first */
		strcat(retbuf, p);

	va_end(argp);

	return retbuf;
}
(Note that there is no semicolon after va_dcl. Note that in this case, no special treatment for the first argument is necessary.) You may also have to declare the string functions by hand rather than using <string.h>.

If you can manage to find a system with vfprintf but without <stdarg.h>, here is a version of the error function (from question 15.5) using <varargs.h>:

#include <stdio.h>
#include <varargs.h>

void error(va_alist)
va_dcl		/* no semicolon */
	{
	char *fmt;
	va_list argp;
	fprintf(stderr, "error: ");
	va_start(argp);
	fmt = va_arg(argp, char *);
	vfprintf(stderr, fmt, argp);
	va_end(argp);
	fprintf(stderr, "\n");
}
(Note that in contrast to <stdarg.h>, under <varargs.h> all arguments are variable, so the fmt argument must also be picked up via va_arg.)

References: H&S Sec. 11.4 pp. 296-9
CT&P Sec. A.2 pp. 134-139
PCS Sec. 11 pp. 184-5, Sec. 13 p. 250




comp.lang.c FAQ list · Question 15.8

Q: How can I discover how many arguments a function was actually called with?


A: This information is not available to a portable program. Some old systems provided a nonstandard nargs function, but its use was always questionable, since it typically returned the number of words passed, not the number of arguments. (Structures, long ints, and floating point values are usually passed as several words.)

Any function which takes a variable number of arguments must be able to determine from the arguments themselves how many of them there are. printf-like functions do this by looking for formatting specifiers (%d and the like) in the format string (which is why these functions fail badly if the format string does not match the argument list). Another common technique, applicable when the arguments are all of the same type, is to use a sentinel value (often 0, -1, or an appropriately-cast null pointer) at the end of the list (see the execl and vstrcat examples in questions 5.2 and 15.4). Finally, if the types are predictable, you can pass an explicit count of the number of variable arguments (although it's usually a nuisance for the caller to supply).

References: PCS Sec. 11 pp. 167-8




comp.lang.c FAQ list · Question 15.9

Q: My compiler isn't letting me declare a function

	int f(...)
	{
	}
i.e. accepting a variable number of arguments, but with no fixed arguments at all.


A: Standard C requires at least one fixed argument, in part so that you can hand it to va_start. (In any case, you often need a fixed argument to determine the number, and perhaps the types, of the variable arguments.) See also question 15.10.

References: ISO Sec. 6.5.4, Sec. 6.5.4.3, Sec. 7.8.1.1
H&S Sec. 9.2 p. 263




comp.lang.c FAQ list · Question 15.10

Q: I have a varargs function which accepts a float parameter. Why isn't

va_arg(argp, float)
working?


A: In the variable-length part of variable-length argument lists, the old ``default argument promotions'' apply: arguments of type float are always promoted (widened) to type double, and types char and short int are promoted to int. Therefore, it is never correct to invoke va_arg(argp, float); instead you should always use va_arg(argp, double). Similarly, use va_arg(argp, int) to retrieve arguments which were originally char, short, or int. (For analogous reasons, the last ``fixed'' argument, as handed to va_start, should not be widenable, either.) See also questions 11.3 and 15.2.

References: ISO Sec. 6.3.2.2
Rationale Sec. 4.8.1.2
H&S Sec. 11.4 p. 297




comp.lang.c FAQ list · Question 15.11

Q: I can't get va_arg to pull in an argument of type pointer-to-function.


A: Try using a typedef for the function pointer type.

The type-rewriting games which the va_arg macro typically plays are stymied by overly-complicated types such as pointer-to-function. To illustrate, a simplified implementation of va_arg is

	#define va_arg(argp, type) \
		(*(type *)(((argp) += sizeof(type)) - sizeof(type)))
where argp's type (va_list) is char *. When you attempt to invoke
	va_arg(argp, int (*)())
the expansion is
	(*(int (*)() *)(((argp) += sizeof(int (*)())) - sizeof(int (*)())))
which is a syntax error (the first cast (int (*)() *) is meaningless). [footnote]

If you use a typedef for the function pointer type, however, all will be well. Given

	typedef int (*funcptr)();
the expansion of
	va_arg(argp, funcptr)
is
	(*(funcptr *)(((argp) += sizeof(funcptr)) - sizeof(funcptr)))
which works correctly.

See also questions 1.13, 1.17, and 1.21.

References: ISO Sec. 7.8.1.2
Rationale Sec. 4.8.1.2




comp.lang.c FAQ list · Question 15.12

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)?


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 of question 15.5:

	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, as Chris Torek has observed, 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; see also question 15.13.)

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.


comp.lang.c FAQ list · Question 15.13

Q: How can I call a function with an argument list built up at run time?


A: There is no guaranteed or portable way to do this. If you're curious, this list's editor has a few wacky ideas you could try...

Instead of an actual argument list, you might consider passing an array of generic (void *) pointers. The called function can then step through the array, much like main() might step through argv. (Obviously this works only if you have control over all the called functions.)

(See also question 19.36.)

Additional links: “wacky ideas”





Read sequentially: prev next up



about this FAQ list   about eskimo   search   feedback   copyright

Hosted by Eskimo North