Thomas Eding
Thomas Eding

Reputation: 1

Does the ordering of multiple va_end calls matter?

I have the following code:

va_list va[2];
va_start(va[0], fmt);
va_start(va[1], fmt);
process(fmt, va);
va_end(va[0]);
va_end(va[1]);

I've looked at various sites for documentation on va_start and va_end, and all they say is that va_end should be invoked for each va_start before the calling function returns.

What I am unsure about is whether or not the order of the calls is significant. In particular, is

va_end(va[0]);
va_end(va[1]);

sementically identical to

va_end(va[1]);
va_end(va[0]);

in the above sample code?

Upvotes: 9

Views: 602

Answers (3)

user743382
user743382

Reputation:

The only relevant requirement in the C99 standard is:

7.15.1 Variable argument list access macros

1 [...] Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.

There is no requirement for the order of multiple va_end invocations to match that of va_start, or to match the reverse of that of va_start, so implementations are required to accept either order.

You could even use a horrible mess like

void f(int a, ...) {
  va_list ap;
  goto b;
a:
  va_end(ap);
  return;
b:
  va_start(ap, a);
  goto a;
}

This matches all requirements of the standard, so implementations must accept it. As a result, even tricks where va_end expands to something with unmatched braces are not allowed.

In practice, I am not even aware of any current implementation in which va_end has any necessary effect at all. All the implementations that I've been able to find, at most set the value (or the first sub-value, depending on the type) to zero, which would make further use of va_arg fail, but would not cause problems if you omit va_end from your code. Most don't even do that. Now, I wouldn't actually remove it from code, since there are legitimate reasons why an implementation (current or future) may actually do something in its va_end, but you can assume that current and future implementations will at least attempt to implement it in a way that matches the standard's requirements.

The historic implementations which use #define va_end(ap) } are just that: history. They did not provide that macro in <stdarg.h>, and they did not even have a <stdarg.h> header. You shouldn't worry about them.

Upvotes: 6

On some [old] implementations, va_start expanded to a left brace { followed by some declaration, and va_end expanded to a right brace } possibly preceded by some "finalization". Morally they should match. In practice, often but not always, order does not matter much (but in principle it does matter).

On recent GCC, these va_start and va_end macros expand to invocations of __builtin_va_start & __builtin_va_end so the compiler may care (perhaps in some future version) that these are correctly nested. See this. So the "good" order should be:

va_list va[2];
va_start(va[0], fmt);
  va_start(va[1], fmt);
     process(fmt, va);
  va_end(va[1]);
va_end(va[0]);

In practice the order of va_end might not matter that much.

The indentation is mine to emphasize that va_start & va_end are "nesting"

Of course, you need to call va_arg to really retrieve variadic arguments (I hope that your process is doing that). stdarg(3) explains that well (for C code):

Each invocation of va_start() must be matched by a corresponding invocation of va_end() in the same function.

Notice the corresponding word (emphasis is mine). I believe it means that va_start and va_end are really nesting (at least in principle).

Upvotes: 1

Kurt
Kurt

Reputation: 297

Just call va_end once for each va_start, but you need to use va_arg to grab the individual arguments. Here's an example: http://www.cplusplus.com/reference/cstdarg/va_start/

Also, I don't think the ordering matters.

Upvotes: 2

Related Questions