Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reuse of variadic arguments

Tags:

c

variadic

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?

like image 317
Thargon Avatar asked May 07 '18 14:05

Thargon


2 Answers

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.

like image 84
Serge Ballesta Avatar answered Sep 29 '22 09:09

Serge Ballesta


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

like image 20
Thargon Avatar answered Sep 29 '22 09:09

Thargon