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_listand 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 typedefs 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