Variable Argument Lists

I’ve often wondered how it works. The printf() function has one argument minimum, a formatting string. Yet how does the compiler know how many other arguments are required? It’s not really a mystery, once you understand the concept of variable argument lists.

Here is the man page format for the printf() function:

int printf(const char * restrict format, ...);

const char * restrict format is a single argument, the first. The second argument is ... (three dots or an ellipses), which is a declaration. It means any number of arguments (including zero) can appear at that point in the function. The official term for such a declaration is a variable argument list.

To process the variable argument list, four macros are used:

va_list
va_start()
va_arg()
va_end()

Each of these is required to read whatever arguments were passed to the function, replacing the ... declaration.

To make the variable argument list work, the first argument passed to the function must set the number of arguments the ... declaration represents. You can’t just pass any old number of arguments. For example:

list( 5, 1, 2, 3, 4, 5 );

The list() function’s declaration is list(int a, ...). The first argument, 5, represents the number of items passed.

In the printf() function, the first argument is parsed internally to determine the number of required arguments. Once you know that value, you can use the four macros to write the code required to read the variable argument list:

The va_list macro declares a variable representing each argument. This variable is used by the other three macros.

The va_start() macro is passed two arguments: the va_list variable and the count value of arguments expected.

The va_arg() macro extracts each argument, one after the other. It requires two arguments: the va_list variable and a data type (int, char, float, and so on).

When all the fun is complete, the va_end() macro terminates the operation, cleaning up whatever memory was used when the va_end variable was declared.

In the following code, the list() function accepts variable arguments. The first argument is the argument count. The rest of the values are displayed from within the function:

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

void list(int a, ... )
{
    va_list arg_list;    /* variable argument list variable */
    int x;

    /* begin processing */
    va_start(arg_list,a);
    printf("Passed:");
    for(x=0;x<a;x++)
    {
        /* fetch values */
        printf(" %2d",va_arg(arg_list,int) );
    }
    putchar('\n');
    /* clean up */
    va_end(arg_list);
}

int main()
{
    list( 5, 1, 2, 3, 4, 5 );
    list( 3, 40, 60, 10 );

    return(0);
}

At Line 6, the va_list variable arg_list is created. It’s used in the rest of the function to handle the variable argument list.

At Line 10, the va_start() macro initializes the list with the arguments arg_list and a, which is the argument count.

The values passed are fetched the for loop at line 13. Each value is represented by the va_arg() macro. Its two arguments are arg_list (the va_list variable) and the variable’s expected data type. For this code, the values are all integers so int is specified.

After the variable list arguments are output, the va_end() macro at Line 19 wraps up the action. Its argument is the arg_list variable.

It’s possible to pass different data types in the variable argument list. The va_arg() macro must set the proper data type, therefore the argument order is important. Or, in the case of the printf() function, the arguments types are stipulated in the format string, so a switch-case structure parses the options using the proper data types in a cascade of va_arg() functions.

Leave a Reply