ILoveC
ILoveC

Reputation: 35

Is va_list always static?

void    va_test2(va_list ap2)
{
    printf("va_test 2 : %d\n", va_arg(ap2, int));
}
void    va_test1(const char* str, ...)
{
    va_list ap1;
    printf("%s\n", str);
    va_start(ap1, str);
    va_test2(ap1);
    printf("va_test 1 : %d\n", va_arg(ap1, int));
    va_test2(ap1);
}
int     main(void)
{
    va_test1("this is a test", 1, 2, 3);
}
result :
    this is a test
    va_test 2 : 1
    va_test 1 : 2
    va_test 2 : 3
result I expected:
    this is a test
    va_test 2 : 1
    va_test 1 : 1
    va_test 2 : 2

In my thought, after va_list 'ap1' is initailized in 'va_test1', it is copied to local variable 'ap2' in 'va_test2'.

So after va_arg(ap, int) increased the va_list 'ap2' in 'va_test2', it should not affect the original va_list 'ap1'.

But the behavior shows that increased argument actually affected 'ap1'.

As far as I know, va_arg is defined

#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

which directly increase the pointer sent.

In my conclusion, va_list seems static behavior wherever it is declared.

Can you tell me is this right, and why it shows static behavior?

Upvotes: 0

Views: 377

Answers (3)

Luis Colorado
Luis Colorado

Reputation: 12708

No, it cannot be static. The reason is that being static will disallow the function to be reentrant, so you should not be able to use a variadic function in different threads.

va_list is a normal type, like a pointer. The only difference is that is a very special pointer that points to the variables in your parameter list, and in fact, depending on the ABI, it can be very special (because it should refer for example to a register in case your ABI allows to pass parameters in registers)

In ancient C compilers, va_list was a simple pointer that was cast to a pointer to the type you pass to the va_arg macro, to be able to do pointer arithmetic with it and advance it to point to the next parameter in the list. These are the semantics associate with the process. But this pointer arithmetic must be special, because different cpus update pointers to the stack normally aligning data in a different way than normal data pointers. This means that for example that if you pass a short number or an int (a 32bit integer) in a 64bit architecture, it is possible that a full 64bit word is pushed to the stack to save a single char (or the integer), for efficiency reasons. Anyway, it is an architecture most dependent part and this is the reason most compilers treat it specially (in gcc it is mapped to __gnuc_va_list). But essentially it is a pointer (possibly passed by reference, you don't know).

There is a way to pass variables by reference in C, which consists in declaring a variable as an array of one element of that variable. I you write the array name in the actual parameter list, you indeed are passing a variable by reference, because the variable name is a reference to the first element of an array of only one element:

typedef void *va_list[1];
#define va_start(_l, _first) do{_l[0] = &(_first)+1;}while(0)
#define va_arg(_l, _typ) (*(_typ *)(_l[0] = (_typ *)_l + 1));
#define va_end(_l)  /* just nothing */

The above code will give you the semantics of a pointer that is passed by reference when you pass it to a function as parameter (because you have defined the type in a way that when you pass it by name you are using a reference)

#include <stdio.h>

/* these are implemented in a library and the user doesn't
 * see the details on how my_type is defined. */
typedef int va_list_fake[1];

void va_start_fake(va_list_fake a, int val)
{
    a[0] = val;
}

int va_arg_fake(va_list_fake a)
{
    return a[0];
}

void exchange(va_list_fake a, va_list_fake b)
{   /* this is not seen by the function user */
    int temp = a[0];
    a[0] = b[0];
    b[0] = temp;
}

void va_end_fake(va_list_fake a)
{
    /* empty */
}

/* now it comes the code in main that doesn't know how is
 * implemented the type va_list_fake */
int main()
{
    va_list_fake a, b;  /* they look as normal variables */

    va_start_fake(a, 3); /* compare this with va_start */
    va_start_fake(b, 2); /* idem. */

    /* print the contents of variables a and b with a simil
     * of function/macro va_arg() that differs from va_arg only
     * in the lack of a second type parameter. */
    printf("a = %d, b = %d\n", va_arg_fake(a), va_arg_fake(b));

    /* exchange, but pass the variables by value, so it should
     * not be updated.  8-. (but there's some hidden trick) */
    exchange(a, b);  /* this will exchange the values */

    /* now a contains the value 2 and b contains the value 3 */
    printf("a = %d, b = %d\n", va_arg_fake(a), va_arg_fake(b));

    va_end_fake(a);
    va_end_fake(b);
}

Upvotes: 0

KamilCuk
KamilCuk

Reputation: 141623

Is va_list always static?

No, it is not. It's usually local.

The behavior of your code is not defined. After the call va_test2(ap1); the only thing you can do with ap1 is to call va_end(ap1). We could read C11 7.16p3:

[...] The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

Still, the behavior of your code could be explained by rejecting your va_arg definition. The behavior you are getting doesn't match the showed definition of the macro, as the macro indeed modifies the value of the variable (unless there's a hidden #define ap *ap :), so rejecting that definition would be the way to move forward.

As far as I know, va_arg is defined

#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

I would say, most probably not. If you are on x86 architecture, then different data types are passed using different registers, as specified by the x86 abi (ex. see page 21 and the whole section around page 52) (and see this answer). That definition could most probably work on a few limited cases. Nowadays, va_arg is usually some compiler magic, like gcc/stdarg.h __builtin_va_arg.

The behavior could be explained if va_list is an array type or a pointer to data on stack. On x86 it's an array of one structure, as defined in the abi above.

// cross my fingers these are right
typedef int va_list[1];
#define va_start(ap, a)   (*ap = (int*)&a);
#define va_arg(ap, t)     (*(t*)((*ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

Because the behavior of your code is not defined, compiler implementators just don't care about how such code will behave - it can behave in any way. As such, you can't result I expected: - you can't expect anything from such code - typically expect nasal demons to spawn.

Upvotes: 2

Eric Postpischil
Eric Postpischil

Reputation: 223747

There is no evidence here that ap1 has static storage duration. There is only evidence that va_test2 has access to the data in or referred to by ap1, which may be because ap1 is an array or a pointer to the data or a structure containing such a pointer.

Upvotes: 1

Related Questions