I wrote a function that accepts a va_list
, and that is meant to be invoked iteratively by its caller. It should modify the va_list
and changes should persist back in the caller so that the next call to the function will proceed with the next argument.
I can't post that code specifically, but here's a snippet that reproduces the situation (godbolt link):
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list ap) {
printf("%i\n", va_arg(ap, int));
}
void vprint_integers(int count, va_list ap) {
for (int i = 0; i < count; ++i) {
print_integer(ap);
}
}
void print_integers(int count, ...) {
va_list ap;
va_start(ap, count);
vprint_integers(count, ap);
va_end(ap);
}
int main() {
print_integers(3, 1, 2, 3);
}
This works (prints "1 2 3") on my x86 platform because va_list
is passed by "reference" (it's probably declared as an array of one element, so va_list
arguments decay to a pointer). However, it does not work on my ARM platform (prints "1 1 1"), where va_list
seems to be defined as a pointer to something. On that platform, va_arg
always returns the first argument.
The next best option seems to be to make ap
a pointer:
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list *ap) {
printf("%i\n", va_arg(*ap, int));
}
void vprint_integers(int count, va_list ap) {
for (int i = 0; i < count; ++i) {
print_integer(&ap);
}
}
void print_integers(int count, ...) {
va_list ap;
va_start(ap, count);
vprint_integers(count, ap);
va_end(ap);
}
int main() {
print_integers(3, 1, 2, 3);
}
This works on ARM (with va_arg(*ap, ...)
), but it does not compile on x86. When I try print_integer(&ap)
on x86, Clang says:
error: incompatible pointer types passing '
struct __va_list_tag **
' to parameter of type 'va_list *
' (aka '__builtin_va_list *
')
This only seems to happen when taking the address of a va_list
passed as an argument, not when it's taken from a local variable. Unfortunately, I do need my v
variant to take a va_list
object and not a pointer to it.
It's easy to get consistent cross-platform value semantics for va_list
using va_copy
. Is there a cross-platform way to get consistent reference semantics for va_list
?
The thing at issue here is that va_list
, on the x86 platform, is defined as an array of 1 element (let's call it __va_list_tag[1]
). It decays to a pointer when accepted as an argument, so &ap
is wildly different depending on whether ap
is a parameter of the function (__va_list_tag**
) or a local variable (__va_list_tag(*)[1]
).
One solution that works for this case is simply to create a local va_list
, use va_copy
to populate it, and pass a pointer to this local va_list
. (godbolt)
void vprint_integers(int count, va_list ap) {
va_list local;
va_copy(local, ap);
for (int i = 0; i < count; ++i) {
print_integer(&local);
}
va_end(local);
}
In my case, vprint_integers
and va_copy
are necessary because the interface of vprint_integers
accepts a va_list
and that cannot change. With more flexible requirements, changing vprint_integers
to accept a va_list
pointer is fine too.
va_list
isn't specified to be anything in particular, but it's defined to be an object type, so there's not really a reason to believe that you can't take or pass its address. Another very similar solution that entirely bypasses the question of whether you can take the address of a va_list
is to wrap the va_list
in a struct and pass a pointer to that struct.