1. High-Level Concept
Normally, a C function has a fixed list of arguments. However, sometimes you need flexibility, such as a printing function that takes a format string and any number of variables to print. To do this, the function must rely on:
- A named argument: You need at least one fixed argument at the start (often a format string) to establish a reference point.
- The Ellipsis (
...): This appears at the end of the argument list in the function declaration to indicate “more arguments follow.” - Macros: Special tools to “walk” through the list of unnamed arguments.
2. The Mechanics: <stdarg.h>
To process these arguments, you use the type va_list and three macros defined in <stdarg.h>:
va_list: A special data type used to declare a variable (often namedapfor “argument pointer”) that moves through the arguments.va_start(ap, lastarg): Initializes the pointer. You must pass it yourva_listvariable and the name of the last named argument in your function declaration.va_arg(ap, type): Returns the next argument. You must specify the type (likeintordouble) you expect effectively casting the raw data found at the pointer.va_end(ap): Cleans up everything before the function returns.
3. Textbook Example: minprintf
The textbook provides a simplified version of printf, called minprintf, to demonstrate this. It processes a format string and prints arguments based on simple codes (%d, %f, %s).
Here is the code breakdown:
#include <stdarg.h>
#include <stdio.h>
/* minprintf: minimal printf with variable argument list */
void minprintf(char *fmt, ...)
{
va_list ap; /* 1. Declare the argument pointer */
char *p, *sval;
int ival;
double dval;
va_start(ap, fmt); /* 2. Initialize ap to point to the first unnamed arg */
/* Loop through the format string */
for (p = fmt; *p; p++) {
if (*p != '%') {
putchar(*p);
continue;
}
/* Switch on the character after '%' */
switch (*++p) {
case 'd':
/* 3. Fetch the next argument assuming it is an int */
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
/* Fetch the next argument assuming it is a double */
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
/* Fetch the next argument assuming it is a char pointer */
for (sval = va_arg(ap, char *); *sval; sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap); /* 4. Clean up */
}4. Key Details
- Type Safety: The compiler cannot check the types of the unnamed arguments. If you pass a
char *but useva_arg(ap, int), you will likely get garbage data or a crash. The function relies entirely on the logic you write (like parsing the%codes) to know what type to grab next. - Default Promotions: In variable argument lists,
floatvalues are automatically promoted todouble, andcharorshortare promoted toint. This is why the example usesva_arg(ap, double)even if you might be thinking of a float, andva_arg(ap, int)for integers. - Initialization: You must call
va_startbefore usingva_arg, and you must callva_endbefore returning.
Practice Question
To test your understanding:
If you were writing a function sum(int count, …) that sums up a list of integers, how would the loop using va_arg look? (Hint: You know the number of arguments because of the count parameter).