va_arg()

Get the next item in a list of variable arguments

Synopsis:

#include <stdarg.h>

type va_arg( va_list param, 
             type );

Arguments:

param
The va_list object that you initialized with the va_start() macro.
type
The type of the next argument.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

You can use the va_arg() macro to get the next argument in a list of variable arguments.


Caution: Take special care when using varargs on some platforms; see "Varargs and coercion," below.

You must use va_arg() with the associated macros va_copy(), va_start() and va_end(). A sequence such as:

void example( char *dst, ... )
{
    va_list curr_arg;
    int next_arg;

    va_start( curr_arg, dst );
    next_arg = va_arg( curr_arg, int );
    ...

causes next_arg to be assigned the value of the next variable argument. The argument type (which is int in the example) is the type of the argument originally passed to the function.


Note: The last argument before the ellipsis (...) has to be an int or a type that doesn't change in size if cast to an int. If the argument is promoted, the ANSI/ISO standard says the behavior is undefined, and so depends on the compiler and the library.

You must execute the macro va_start() first, in order to initialize the variable curr_arg properly, and execute the macro va_end() after getting all the arguments.

The data item curr_arg is of type va_list that contains the information to permit successive acquisitions of the arguments.

The following functions use a "varargs" list:

verr()
verrx()
vfprintf()
vfscanf()
vfwprintf()
vfwscanf()
vprintf()
vscanf()
vslogf()
vsnprintf()
vsprintf()
vsscanf()
vswprintf()
vswscanf()
vsyslog()
vwarn()
vwarnx()
vwprintf()
vwscanf()

Varargs and coercion

On some platforms, such as PowerPC, the va_list type is an array; on other platforms, such as x86, it isn't. This can lead to problems.

Consider the following example. It seems correct, but on PowerPC platforms, it doesn't print 2:

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

void handle_foo(char *fmt, va_list *pva) {
   printf("%d\n", va_arg(*pva, int));
}

void vfoo(char *fmt, va_list va) {
   handle_foo(fmt, &va);
}

void foo(char *fmt, ...) {
   va_list va;

   va_start(va, fmt);
   vfoo(fmt, va);
   va_end(va);
}

int main() {
   foo("", 2);
   return 0;
}

The C standard says that prototypes such as vfoo() have the array type silently coerced to be a pointer to a base type. This makes things work when you pass an array object to the function. An array-typed expression is converted to a pointer to the first element when used in an rvalue context, so the coercion in the function makes everybody happy.

The problem occurs when you then pass the address of the va_list parameter to another function. The function expects a pointer to the array, but what it really gets is a pointer to a pointer (because of the original conversion). If you use the va_list type in the second function, you won't get the right data.

Here's the example modified so that it works in all cases:

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

void handle_foo(char *fmt, va_list *pva) {
   printf("%d\n", va_arg(*pva, int));
}

void vfoo(char *fmt, va_list va) {
   va_list temp;

   va_copy(temp, va);
   handle_foo(fmt, &temp);
   va_end(temp);
}

void foo(char *fmt, ...) {
   va_list va;

   va_start(va, fmt);
   vfoo(fmt, va);
   va_end(va);
}

int main() {
   foo("", 2);
   return 0;
}

Using va_copy() "undoes" the coercion that happens in the parameter list, so that handle_foo() gets the proper data.

Returns:

The value of the next variable argument, according to type passed as the second parameter.

Examples:

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

static void test_fn( const char *msg,
                     const char *types,
                     ... );

int main( void )
  {
    printf( "VA...TEST\n" );
    test_fn( "PARAMETERS: 1, \"abc\", 546",
         "isi", 1, "abc", 546 );
    test_fn( "PARAMETERS: \"def\", 789",
         "si", "def", 789 );
    return EXIT_SUCCESS;
  }

static void test_fn(
  const char *msg,   /* message to be printed     */
  const char *types, /* parameter types (i,s)     */
  ... )          /* variable arguments     */
  {
    va_list argument;
    int   arg_int;
    char *arg_string;
    const char *types_ptr;

    types_ptr = types;
    printf( "\n%s -- %s\n", msg, types );
    va_start( argument, types );
    while( *types_ptr != '\0' ) {
      if (*types_ptr == 'i') {
        arg_int = va_arg( argument, int );
        printf( "integer: %d\n", arg_int );
      } else if (*types_ptr == 's') {
        arg_string = va_arg( argument, char * );
        printf( "string:  %s\n", arg_string );
      }
      ++types_ptr;
    }
    va_end( argument );
  }

produces the output:

VA...TEST

PARAMETERS: 1, "abc", 546 -- isi
integer: 1
string:  abc
integer: 546

PARAMETERS: "def", 789 -- si
string:  def
integer: 789

Classification:

POSIX 1003.1

Safety:
Cancellation point No
Interrupt handler Yes
Signal handler Yes
Thread Yes

Caveats:

va_arg() is a macro.

See also:

va_copy(), va_end(), va_start()