I have a question regarding restarting variadic argument lists (va_list
).
Basically I want to do something like this:
void someFunc(char* fmt, ...) {
va_list ap;
va_start(fmt, ap);
otherFuncA(fmt, ap);
// restart ap
otherFuncB(fmt, ap);
// restart ap
...
va_end(ap);
return;
}
Now my question is: How to restart ap
?
Please note that this question is not related to C++ but C.
I found two possible solutions so far, but I would like to know which one is "correct" or "best practice".
Solution 1: multiple va_start()
With GCC7 I can replace the lines
// restart ap
in the example above by
va_end(ap);
va_start(fmt, ap);
to reset ap
to the first argument.
However, I am not sure whether this is actually valid code or I am just lucky that some undefined behavior did not corrupt the result.
Solution 2: va_copy()
Another solution that works fine with GCC7 is to initialize several copies of ap
using va_copy()
like
void someFunc(char* fmt, ...) {
va_list ap1, ap2;
va_start(fmt, ap1);
va_copy(ap2, ap1);
otherFuncA(fmt, ap1);
otherFuncB(fmt, ap2);
va_end(ap1);
va_end(ap2);
return;
}
This is valid code (imo) but since there are multiple va_list
instances now which need to be copied, it is much less efficient than the first solution.
So which solution is the best? Is it one of the two I mentioned above, or something completely different?
The va_copy
way is valid: this is exactly what va_copy
was made for. You say that it is much less efficient than the first solution. I do not really agree. First it is an implementation detail, but variadic argument list are commonly implemented in C as a pointer in the parameter stack, pointing on next argument to be retrieved by va_arg
. So va_copy
does not duplicate the argument list, but just a mere pointer.
But restarting the list by va_arg
is valid too. Draft n1570 for C11 says at 7.16.1.3 The va_end macro (emphasize mine):
... The va_end macro may modify ap so that it is no longer usable (without being reinitialized by the va_start or va_copy macro).
My understanding is that it is legal to re-initialize the processing of the variadic arguments list with a new va_arg
after the first va_end
.
The difference between both ways, is that va_copy
allows concurrent views of the same list, while reinitialization with va_start
only allows sequential views (first is closed before second is opened).
My opinion is that the criteria for choice should not be performance, because the overhead of va_copy
should be neglectible in decent implementations, but your real requirements: if you want only one view at a time on the list, stick to va_arg
reinitialization, if concurrent lists allow cleaner of simpler processing, do use it with the help of va_copy
.
Thank you all for your helpful comments!
I tried several more approaches and I think I finally found two good solutions.
The first one is just solution 1 as described in my original question with the edit (thanks to rici).
The second one can be applied for more complex applications (as it turned out was required in my case). Let's start with the code:
void valistFunc(char* fmt, va_list ap) {
va_list apcpy;
for (/*all consecutive calls*/) {
va_copy(apcpy, ap);
otherFuncX(fmt, apcpy); // a different function for each iteration
va_end(apcpy);
}
return;
}
void variadicFunc(char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
valistFunc(fmt, ap);
va_end(ap);
return;
}
First of all, two functions are required if you have multiple methods like variadicFunc
but you want to keep some of the logic in a single place (valistFunc
).
However, you cannot pass on ...
as argument to subsequent functions, so you need to setup an according va_list
object.
Then again, GCC complains (warns) if you try to use va_start
in a function that does not take ...
as argument, why you cannot (or should not) use va_start
in valistFunc
(note: I don't know why GCC complains, I just assume there is some reason behind this behavior).
Instead, you need to instantiate a single additional copy apcpy
that you can re-purpose as often as you like via va_copy
and va_end
.
I hope this might help someone else ;)
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