When trying to compile this code
#include <stdarg.h>
void bar_ptr(int n, va_list *pvl) {
// do va_arg stuff here
}
void bar(int n, va_list vl) {
va_list *pvl = &vl; // error here
bar_ptr(n, pvl);
}
void foo(int n, ...) {
va_list vl;
va_list *pvl = &vl; // fine here
va_start(vl, n);
bar(n, vl);
va_end(vl);
}
int main() {
foo(3, 1, 2, 3);
return 0;
}
the GCC compiler prints a warning about initialization from incompatible pointer type
in the bar
function. The identical statement is fine in foo
.
It seems that the type of an agument of type va_list
is not a va_list
. This can be tested easily with a static assertion like
_Static_assert(sizeof(vl) == sizeof(va_list), "invalid type");
in the bar
function. With GCC, the _Static_assert
fails. The same can be tested also in C++ with declytpe
and std::is_same
.
I would like to take the address of the va_list vl
argument of bar
, and pass it as argument of bar_ptr
, to do thinks like the one described in this thread. On the other hand, it is fine to call bar_ptr(n, pvl)
directly from main
, replacing bar(n, vl)
.
According to the footnote 253 of the C11 final draft,
It is permitted to create a pointer to a
va_list
and pass that pointer to another function
Why this cannot be done if va_list
is defined as argument of the function, and not in the function body?
Workaround:
Even if this does not answer the question, a possible workaround is to change the content of bar
by using a local copy of the argument created with va_copy:
void bar(int n, va_list vl) {
va_list vl_copy;
va_copy(vl_copy, vl);
va_list *pvl = &vl_copy; // now fine here
bar_ptr(n, pvl);
va_end(va_copy);
}
va_list is a complete object type suitable for holding the information needed by the macros va_start, va_copy, va_arg, and va_end. If a va_list instance is created, passed to another function, and used via va_arg in that function, then any subsequent use in the calling function should be preceded by a call to va_end.
The type va_list is used for argument pointer variables.
The va_list may be passed as an argument to another function, but calling va_arg() within that function causes the va_list to have an indeterminate value in the calling function. As a result, attempting to read variable arguments without reinitializing the va_list can have unexpected behavior.
In the most usual stack-based situation, the va_list is merely a pointer to the arguments sitting on the stack, and va_arg increments the pointer, casts it and dereferences it to a value. Then va_start initialises that pointer by some simple arithmetic (and inside knowledge) and va_end does nothing.
va_list
is permitted by the standard to be an array, and often it is.
That means va_list
in a function argument gets adjusted to a pointer to whatever va_list
's internal first element is.
The weird rule (7.16p3) regarding how va_list
gets passed basically accommodates the possibility that va_list
might be of an array type or of a regular type.
I personally wrap va_list
in a struct
so I don't have to deal with this.
When you then pass pointers to such a struct va_list_wrapper
, it's basically as if you passed pointers to va_list
, and then footnote 253 applies which gives you the permission to have both a callee and a caller manipulate the same va_list
via such a pointer.
(The same thing applies to jmp_buf
and sigjmp_buf
from setjmp.h
. In general, this type of array to pointer adjustment is one of the reasons why array-typed typedef
s are best avoided. It just creates confusion, IMO.)
Another solution (C11+ only):
_Generic(vl, va_list: &vl, default: (va_list *)vl)
Explanation: if vl
has type va_list
, then va_list
isn't an array type and just taking the address is fine to get a va_list *
pointing to it. Otherwise, it must have array type, and then you're permitted to cast a pointer to the first element of the array (whatever type that is) to a pointer to the array.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With