                     VARIABLE ARGUMENT LISTS IN C

                            Matthew Probert
                           Servile  Software


Some functions, such as printf(), accept a variable number and type of 
arguments. C provides a mechanism to write your own functions which 
can accept a variable argument list. This mechanism is the va_ family 
defined in the header file 'stdarg.h'. 

There are three macros which allow implementation of variable argument 
lists; va_start(), va_arg() and va_end() and a variable type va_list 
which defines an array which holds the information required by the 
macros. 

va_start() takes two parameters, the first is the va_list variable and 
the second is the last fixed parameter sent to the function. 
va_start() must be called before attempting to access the variable 
argument list as it sets up pointers required by the other macros. 

va_arg() returns the next variable from the argument list. It is 
called with two parameters, the first is the va_list variable and the 
second is the type of the argument to be extracted. So, if the next 
variable in the argument list is an integer, it can be extracted with; 

    <int> = va_arg(<va_list>,int); 

va_end() is called after extracting all required variables from the 
argument list, and simply tidies up the internal stack if appropriate. 
va_end() accepts a single parameter, the va_list variable. 

The following simple example program illustrates the basis for a 
printf() type implementation where the types of the arguments is not 
known, but can be determined from the fixed parameter. This example 
only caters for integer, string and character types, but could easily 
by extended to cater for other variable types as well by following the 
method illustrated; 

#include <stdarg.h> 

char *ITOS(long x, char *ptr) 
{ 
    /* Convert a signed decimal integer to a string */ 

    long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 
                   1000, 100, 10, 1 }; 
    int n; 

    /* Check sign */ 
    if (x < 0)
    { 
        *ptr++ = '-'; 
        /* Convert x to absolute */ 
        x = 0 - x; 
    } 

    for(n = 0; n < 9; n++)
    { 
        if (x > pt[n])
        { 
            *ptr++ = 48 + x / pt[n]; 
            x %= pt[n]; 
        } 
    } 
    return(ptr); 
} 

void varfunc(char *format, ...) 
{ 
    va_list arg_ptr; 
    char output[1000]; 
    char *ptr; 
    int bytes; 
    int x; 
    char *y; 
    char z; 

    /* Initialise pointer to argument list */ 
    va_start(arg_ptr, format); 

    /* loop format string */ 
    ptr = output; 
    bytes = 0; 
    while(*format)
    { 
        /* locate next argument */ 
        while(*format != '%')
        { 
            *ptr++ = *format++; 
            bytes++; 
        } 
        /* A % has been located */ 
        format++; 
        switch(*format)
        { 
            case '%' :  *ptr++ = '%'; 
                        break; 

            case 'd' : /* integer expression follows */ 
                       x = va_arg(arg_ptr,int); 
                       ptr = ITOS(x,ptr); 
                       *ptr = 0; 
                       format++; 
                       bytes += strlen(output) - bytes; 
                       break; 
    
            case 's' : /* String expression follows */ 
                       y = va_arg(arg_ptr,char *); 
                       strcat(output,y); 
                       x = strlen(y); 
                       format++; 
                       ptr += x; 
                       bytes += x; 
                       break; 

            case 'c' : /* Char expression follows */ 
                       z = va_arg(arg_ptr,char); 
                       *ptr++ = z; 
                       format++; 
                       bytes++; 
                       break; 
        } 

    } 

    /* Clean stack just in case! */ 
    va_end(arg_ptr); 

    /* Null terminate output string */ 
    *ptr = 0; 

    /* Display what we got */ 
    printf("\nOUTPUT==%s",output); 
} 


void main()
{ 
    varfunc("%d %s %c",5,"hello world",49); 
} 


A simpler variation is to use vsprintf() rather than implementing our 
own variable argument list access. However, it is beneficial to 
understand how variable argument lists behave. The following is a 
simplification of the same program, but leaving the dirty work to the 
compiler; 

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

void varfunc(char *format, ...) 
{ 
    va_list arg_ptr; 
    char output[1000];          /* ! */

    va_start(arg_ptr, format); 

    vsprintf(output,format,arg_ptr); 

    va_end(arg_ptr); 

    /* Display what we got */ 
    printf("\nOUTPUT==%s",output); 

} 

void main()
{ 
    varfunc("%d %s %c",5,"hello world",49); 
} 

This example uses a fixed length buffer, 'output[]', to receive the 
variable output. It is quite conceivable that this could be overflowed 
by execssive text being supplied to the function. A better idea would 
be to use dynamic memory allocation. However, this example has been 
chosen for simplicty, and not as a commercial application.
