va_list argument actually is not a va_list

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.