Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

va_list argument actually is not a va_list

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);
}
like image 722
Giovanni Cerretani Avatar asked Feb 04 '20 10:02

Giovanni Cerretani


People also ask

What does va_list mean?

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.

Is va_list a pointer?

The type va_list is used for argument pointer variables.

Can you pass va_list to another function?

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.

How does va_ list work?

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.


2 Answers

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.)

like image 62
PSkocik Avatar answered Oct 15 '22 19:10

PSkocik


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.

like image 22
R.. GitHub STOP HELPING ICE Avatar answered Oct 15 '22 19:10

R.. GitHub STOP HELPING ICE