Is GCC mishandling a pointer to a va_list passed to a function?

pass va_list to another function
c va_list example
va_start used in function with fixed args
va_list definition

The question 'Pass va_list or pointer to va_list?' has an answer which quotes the standard (ISO/IEC 9899:1999 - §7.15 'Variable arguments <stdarg.h>, footnote 212) as explicitly saying that:

It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.

I'm compiling some code which can be exemplified by the following (the real code is very considerably more complex, with the original functions doing a lot more work than shown here).

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

static void test_ptr(const char *fmt, va_list *argp)
{
    int x;
    x = va_arg(*argp, int);
    printf(fmt, x);
}

static void test_val(const char *fmt, va_list args)
{
    test_ptr(fmt, &args);
}

static void test(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);   /* First use */
    test_val(fmt, args);
    va_end(args);
    va_start(args, fmt);   /* Second use */
    test_ptr(fmt, &args);
    va_end(args);
}

int main(void)
{
    test("%d", 3);
    return 0;
}
Error messages

When I compile it (on RHEL5 with GCC 4.1.2 or 4.5.1), I get the following error messages. Notice how much more informative the 4.5.1 error message is - the GCC team is to be congratulated on the improvement!

$ gcc --version
gcc (GCC) 4.5.1
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ /usr/bin/gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13:5: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
vap.c:4:13: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’
$ /usr/bin/gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
$ 

I get the same messages on MacOS X Lion with GCC/LLVM 4.2.1 and with GCC 4.6.1:

$ /usr/bin/gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc --version
gcc (GCC) 4.6.1
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$
Questions
  • Can someone articulate why the test_val() function cannot pass the va_list passed as an argument to test_ptr(), whereas the test() function (which created the va_list) can?

  • Is GCC correct to complain about the indirect passing of the pointer in test_val()?

On both cases, I can see an answer fuzzily, but I can't describe it succinctly. I think that the code in test_val() is abusing the va_list and it is good that the code won't compile - but I'd like to be sure before I go fixing it.


Update 2012-03-30

I went to deal with the problematic code this week. Before making changes, I went to find where the miscreant functions are used — and they aren't! So, I solved my compilation error problem by removing the functions (4 externally visible but unused ones, plus 2 static ones which contained the problematic code). That was much simpler than having to work out how to deal with the mess. (This also explains why there was never any evidence of a run-time problem caused by the code.)

This is a known problem. On some architectures (in particular x86-64), va_list needs to be more complex than a simple pointer to the stack, for example because some arguments might be passed in registers or out-of-band in some other way (see this answer for the definition of va_list on x86-64).

On such architectures, it is common to make va_list an array type so that parameters of type va_list will be adjusted to pointer types, and instead of the whole structure, only a single pointer needs to be passed.

This should not violate the C standard, which only says that va_list must be a complete object type and even explicitly accounts for the fact that passing a va_list argument might not actually clone the necessary state: va_list objects have indeterminate value if they are passed as arguments and consumed in the called function.

But even if making va_list an array type is legal, it still leads to the problems you experienced: As parameters of type va_list have the 'wrong' type, eg struct __va_list_tag * instead of struct __va_list_tag [1], it will blow up in cases where the difference between arrays and pointers matter.

The real problem is not the type mismatch gcc warns about, but the by-pointer instead of by-value argument passing semantics: &args in test_val() points to the intermediate pointer variable instead of the va_list object; ignoring the warning means that you'll invoke va_arg() in test_ptr() on the pointer variable, which should return garbage (or segfault if you're lucky) and corrupt the stack.

One workaround is to wrap your va_list in a structure and pass that around instead. Another solution I've seen in the wild, even here on SO, is to use va_copy to create a local copy of the argument and then pass a pointer to that:

static void test_val(const char *fmt, va_list args)
{
    va_list args_copy;
    va_copy(args_copy, args);
    test_ptr(fmt, &args_copy);
    va_end(args_copy); 
}

This should work in practice, but technically it might or might not be undefined behaviour, depending on your interpretation of the standard:

If va_copy() is implemented as a macro, no parameter adjustments are performed, and it might matter that args is not of type va_list. However, as it is unspecified whether va_copy() is a macro or a function, one might argue that it at least could be a function and parameter adjustments are implicitly assumed in the prototype given for the macro. It might be a good idea to ask the officials for clarification or even file a defect report.

You could also use your build system to deal with the issue by defining a configuration flag like HAVE_VA_LIST_AS_ARRAY so you can do the right thing for your particular architecture:

#ifdef HAVE_VA_LIST_AS_ARRAY
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) ((va_list *)(arg))
#else
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) (&(arg))
#endif

static void test_val(const char *fmt, va_list args)
{
    test_ptr(fmt, MAKE_POINTER_FROM_VA_LIST_ARG(args));
}

gcc error message passing a pointer to va_list, Annoyingly va_list is defined __va_list_tag [1] in the libc header file, so the /is- gcc-mishandling-a-pointer-to-a-va-list-passed-to-a-function. The va_arg, va_copy, va_end, and va_start macros provide a portable way to access the arguments to a function when the function takes a variable number of arguments. There are two versions of the macros: The macros defined in STDARG.H conform to the ISO C99 standard; the macros defined in VARARGS.H are deprecated but are retained for backward

The problem is not specific to va_list. The following code results in a similar warning:

typedef char *test[1];

void x(test *a)
{
}

void y(test o)
{
    x(&o);
}

The problem stems from the way C handles function variables that are also arrays, probably due to the fact that arrays are passed as reference and not by value. The type of o is not the same as the type of a local variable of type test, in this case: char *** instead of char *(*)[1].

Returning to the original issue at hand, the easy way to work around it is to use a container struct:

struct va_list_wrapper {
    va_list v;
};

and there would be no typing problems passing a point to it.

va_copy() with GCC and old c++ standards � Issue #1085 � ocornut , You initialize an argument pointer variable of type va_list using va_start . you can pass the va_list variable as an argument to another function and perform all� 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. 253 253) It is permitted to create a pointer to a va_list and pass that pointer to

As others have noted, this issue manifests itself when va_list is an array type. This is allowed by the standard, which only says that va_list must be an "object type".

You can fix the test_val() function like so:

static void test_val(const char *fmt, va_list args)
{
    va_list args_copy;

    /* Note: This seemingly unnecessary copy is required in case va_list
     * is an array type. */
    va_copy(args_copy, args);
    test_ptr(fmt, &args_copy);
    va_end(args_copy);
}

Receiving Arguments (The GNU C Library), The restrictions mentioned are that the va_list pointer becomes invalid after a call to vprintf: http://www.gnu.org/software/libc/ being passed "by value", i.e. the va_list argument to vprintf is passed by value, thus the The problem applies to the family of vprintf functions: vprintf, vfprintf, vsprintf and vsnprintf. To do this, one will have to make a copy of the current value of the argument. But va_list is an opaque type and one cannot necessarily assign the value of one variable of type va_list to another variable of the same type. Macro: void va_copy (va_list dest, va_list src) Macro: void __va_copy (va_list dest, va_list src)

I think va_list must be declared as an array type, which “boils down” to a pointer type when declared as a parameter of a function. Therefore, the & applied to the va_list type in test_val yields a pointer to a pointer type, not a pointer to an array type, however, the test_ptr function declares one of its parameters as a pointer to array type, which is what is actually provided in the test function.

18982 – va_list and vprintf, 我有一些代码可以将变量参数转换为 va_list ,然后将列表传递给一个函数,然后调用 找到这个:Is GCC mishandling a pointer to a va_list passed to a function? ] Which means compilation complains bitterly like: /tmp/t.c: In function `va': /tmp/t.c:10: warning: passing arg 1 of `__builtin_stdarg_start' from incompatible pointer type /tmp/t.c:12: first argument to `va_arg' not of type `va_list' /tmp/t.c:13: first argument to `va_arg' not of type `va_list' /tmp/t.c:14: first argument to `va_arg' not of

c, Suppose I have a function which takes variadic arguments () or a va_list Is GCC mishandling a pointer to a va_list passed to a function? @codz01 There are cases where such function includes a printf() in it and the user might want to validate the function's calling and of course NULL is a valid input wherever a pointer is taking place.

傳遞va_list或指向va_list的指針?, If mishandled, it can lead to unintended consequences and “illegal” actions in the software. In this function, GCC reasons that if p and q are the same, then the debug messages, to pass pointers that can be either null pointers or The stdarg .h header provides four variadic macros: va_start , va_arg� One way you could make the function would be to accept a pointer to an array. Another way would be to write a function that can take any number of arguments. So you could write avg(4, 12.2, 23.3, 33.3, 12.1); or you could write avg(2, 2.3, 34.4); The advantage of this approach is that it's much easier to change the code if you want to change

Technical Article – TrustInsoft, Pointer to the initialized list of arguments to copy to dest. va_arg returns the current argument. va_copy, va_start and va_end do not return values. first optional argument in the list of arguments that's passed to the function. (In reply to comment #5) Thanks for the pointer.Let me try again to explain why I object to the footnote: The footnote assumes that the reader will make the extrapolation that 1) since va_list is an object type, and 2) since array types are (also) object types, and 3) since array types in function arguments are converted to pointers, the actual type of a va_list object when declared as a

Comments
  • I would think that even the Standard would allow va_copy to be implemented as a function on platforms where doing so would yield correct semantics, it would implicitly require that it be implemented as a macro in cases where the correct semantics could not be otherwise achieved.
  • The sentence starting "The type of o" should end in char ** vs char *[1]. The actual types given are the types of &o versus a.