Does multiple va_end calls make sense?

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 looked at various sites for documentation on va_start and va_end , and all they say is that va_end should be called for each va_start before returning the call function.

I am not sure how significant the order of calls is. In particular,

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

identically

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

in the above code example?

+6
source share
3 answers

In some [old] implementations, va_start expands to the left bracket { followed by some declaration, and va_end to the right bracket } , which may precede some “finalization”. Morally, they must match. In practice, often, but not always, order does not matter much (but in principle it matters).

In recent GCC, these macros va_start and va_end expanded to invocations __builtin_va_start and __builtin_va_end , so the compiler may take care (possibly in some future version) that they are correctly nested. Cm. . Thus, a “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 may not matter.

Indentation of mine to emphasize that va_start and va_end are nesting

Of course, you need to call va_arg to really get the va_arg arguments (I hope your process does this). stdarg (3) explains what is good (for C code):

Each call to va_start() must correspond to a corresponding call to va_end() in the same function.

Pay attention to the corresponding word (emphasis mine). I believe this means that va_start and va_end really nested (at least in principle).

+1
source

The only relevant requirement in the C99 standard:

7.15.1 Macros for accessing a list of variable variables

1 [...] Each call to the va_start and va_copy must correspond to the corresponding call to the va_end macro in the same function.

There is no need for the order of several va_end calls to match the va_start request or match the reverse sign of va_start , so implementations should accept any order.

You can even use a terrible mess like

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

This meets all the requirements of the standard, so implementations must accept it. As a result, even tricks in which va_end expand to something with unsurpassed parentheses are not allowed.

In practice, I don’t even know about any current implementation in which va_end has any necessary effect whatsoever. All the implementations that I could find in most cases set the value (or the first sub-bacterial value, depending on the type) to zero, which will lead to further use of va_arg , but will not cause problems if you omit va_end from your code. Most do not even do this. Now I would not remove it from the code, as there are legitimate reasons why an implementation (current or future) can really do something in its va_end , but you can assume that current and future implementations will at least try to implement It complies with standard requirements.

Historical implementations that use #define va_end(ap) } are simple: history. They did not provide this macro in <stdarg.h> , and they did not even have a <stdarg.h> header. You do not have to worry about them.

+3
source

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

Also, I don't think that order matters.

+2
source

Source: https://habr.com/ru/post/974771/


All Articles