Jimm Chen
Jimm Chen

Reputation: 3787

How to use va_list object in a nested way, especially on gcc x64 compiler

Please look through my small C/C++ program.

// va_nest.c
#include <stdarg.h>
#include <stdio.h>

void nest2(const char *fmt, ...)
{
    va_list args2, args_dig;
    int *pi, i;

    va_start(args2, fmt);

    args_dig = va_arg(args2, va_list); 
        // !!! try to fetch the nested va_list object.
    pi = va_arg(args_dig, int*);
    i = va_arg(args_dig, int);

    printf("Inner: @%p , %d\n", pi, i);

    va_end(args2);
}

void nest1(const char *fmt, ...)
{
    va_list args1;
    va_start(args1, fmt);

    nest2(fmt, args1);

    va_end(args1);
}

int main()
{
    int var = 11;
    printf("Outer: @%p , %d\n", &var, var);
    nest1("abc", &var, var);
    return 0;
}

On openSUSE 13.1 x86, it outputs just want I expect(no matter using gcc or g++),

Outer: @0xbfa18d8c , 11
Inner: @0xbfa18d8c , 11

However, it FAILS with x86_64 compiler on openSUSE 13.1 x64 machine.

If I compile it with gcc, it compiles ok, but output is unexpected.

$ gcc -o vac va_nest.c

$ ./vac
Outer: @0x7fffb20f766c , 11
Inner: @0x1b01000000636261 , 4209411

If I compile it with g++, it even fails to compile.

$ g++ -o vacpp va_nest.c
va_nest.c: In function ‘void nest2(const char*, ...)’:
va_nest.c:12:11: error: invalid array assignment
  args_dig = va_arg(args2, va_list);
           ^

gcc and g++ version is 4.8.1

Can anybody help me with that? I hope to achieve the same result on x64 as in Linux x86 compiler.

Upvotes: 2

Views: 2077

Answers (2)

Jimm Chen
Jimm Chen

Reputation: 3787

I realize passing va_list object's address(instead of object itself) is feasible, verified on linux gcc and MSVC 2008(32-bit & 64-bit compiler).

// va_nesta.c
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>

void nest2(const char *fmt, ...)
{
    va_list args2, *args_dig;
    int *pi, i;

    va_start(args2, fmt);

    args_dig = va_arg(args2, va_list*); 
        // !!! ★ try to fetch the nested va_list object's address.
    pi = va_arg(*args_dig, int*);
    i = va_arg(*args_dig, int);

    printf("Inner: @%p , %d\n", pi, i);

    va_end(args2);
}

void nest1(const char *fmt, ...)
{
    va_list args1;
    va_start(args1, fmt);

    nest2(fmt, &args1); // ★use va_list object's address!

    va_end(args1);
}

int main()
{
    int var = 11;
    printf("Outer: @%p , %d\n", &var, var);
    nest1("abc", &var, var);


    return 0;
}

Upvotes: 3

Aaron Digulla
Aaron Digulla

Reputation: 328774

Don't try to be clever with va_list. Instead, use code like this:

nest2(fmt, args1);

(i.e. make sure that all functions use the same structure hidden behind va_list). Then you can define the second function like so:

void nest2(const char *fmt, va_list args1)

Also make sure that you call va_end() exactly once. Nesting won't work reliably (i.e. if it works, that's more luck than anything else).

If in doubt, look at the source code of GLIBC, especially fprintf.c and vfprintf.c.

int
__fprintf (FILE *stream, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = vfprintf (stream, format, arg);
  va_end (arg);

  return done;
}

and later:

int
vfprintf (FILE *s, const CHAR_T *format, va_list ap)
{

Upvotes: 6

Related Questions