There is one thing that always puzzles me about va_end()
. I often read that is not an actual function, but a preprocessor macro. Although this might sound like an insignificant detail, it could actually influence where va_end()
needs to be invoked.
The question simply is: Does va_end()
need to be invoked before each return
statement in a variadic function with multiple return
statements?
The variadic function in the following example has the trivial task of returning the first non-NULL
of its arguments. As you can see there are three return
statements in the function body. One of them appears after va_start()
but before va_end()
.
Is this code correct?
#include <stdio.h>
#include <stdarg.h>
static const void * const FIRST_NON_NULL_END = (void *) "";
void * first_non_null (
const void * const ptr1,
...
) {
if (ptr1) {
return ptr1 == FIRST_NON_NULL_END ? NULL : (void *) ptr1;
}
void * retval;
va_list args;
va_start(args, ptr1);
do {
retval = va_arg(args, void *);
if (retval == FIRST_NON_NULL_END) {
/* Is this correct? Here I do not invoke `va_end()`! */
return NULL;
}
} while (!retval);
va_end(args);
return retval;
}
int main () {
const char * const my_string = first_non_null(
NULL,
NULL,
"autumn",
NULL,
"rose",
FIRST_NON_NULL_END
);
printf("The first non-null value is: \"%s\".\n", my_string);
return 0;
}
EDIT:
Just to clarify, the example above was written in that form for didactic purposes only. In real life it can be rewritten in a much nicer and synthetic way as:
#include <stdio.h>
#include <stdarg.h>
static const void * const FIRST_NON_NULL_END = (void *) "";
void * first_non_null (
const void * const ptr1,
...
) {
const void * retval;
va_list args;
va_start(args, ptr1);
for (retval = ptr1; !retval; retval = va_arg(args, const void *))
;;
va_end(args);
return retval == FIRST_NON_NULL_END ? NULL : (void *) retval;
}
int main () {
const char * const my_string = first_non_null(
NULL,
NULL,
"autumn",
NULL,
"rose",
FIRST_NON_NULL_END
);
printf("The first non-null value is: \"%s\".\n", my_string);
return 0;
}
From the C Standard (7.16.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.
So if va_start
was used before any return statement and va_end
was not yet invoked then it shall be invoked.