10

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);
}
12
  • 2
    Regarding the standards quote you have: You don't actually pass a pointer to a va_list. Commented Feb 4, 2020 at 11:00
  • 4
    It is permitted to create a pointer to a va_list and pass that pointer to another function You're not passing a pointer to a va_list, you're passing an actual va_list. Commented Feb 4, 2020 at 11:01
  • I know I'm passing a va_list. Suppose I have a third function void bar_ptr(va_list *pvl);, I want to pass a pointer to va_list vl to that function. I'll edit the question to specify it. Commented Feb 4, 2020 at 11:02
  • 1
    This and this question could be helpful. And it's also very likely that the compiler have special handling of va_list Commented Feb 4, 2020 at 11:43
  • 1
    The va_copy approach you have is the canonical solution to this problem. I have a few past questions on it that basically concluded this. Commented Feb 4, 2020 at 13:48

2 Answers 2

6

va_list is permitted by the standard to be an array, and often it is. That means va_list in a function parameter 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.)

Sign up to request clarification or add additional context in comments.

8 Comments

If va_list may be an array, it is misleading for the standard to say “The object ap may be passed as an argument to another function…” (C 2018 7.16 3, ap is an object of type va_list).
@EricPostpischil: The standard says a lot of misleading things and considers them not-a-but/WONTFIX as long as the intent is clear to "somebody"... :-P
Wouldn't it make more sense to just do as suggested by footnote 253 and pass a pointer to the va_list instead of the va_list itself? That will also work whether or not va_list is an array type, and avoids copying a possibly bulky object. (Sorry to comment after so much time. Your answer came up during a search for problems caused by va_list sometimes being an array type.)
@rici Wrapping a might-be-an-array type in a struct gives it more predictable semantics from the user's point of view: passing an address to it gives you reference semantics, passing it gives you value semantics and you don't get the highly unintuitive thing where might_be_an_array_t block_scope_var; is typed differently from might_be_an_array_t arg_variable;. Unfortunately for a struct wrapped_va_list, the standard doesn't really guarantee that copies of that will work as expected as the standard wants you to do va_copy+va_end to get a copy of the current state.
@rici And yes, you usally want reference semantics anyway and it doesn't matter if you get that by passing struct wrapped_va_list* or always passing va_list* (might as well always pass va_list, because you never get value semantics with it anyway--you either get reference semantics if the caller passing a va_list doesn't use the passed ap after the call (other than doing va_end(ap) on it) or you get undefined behavior if it tries to use it after passing it to a callee).
|
3

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.