Search code examples
cvariadic-functionsc-standard-library

Does `va_end()` need to be invoked before each `return` statement in a variadic function with multiple `return` statements?


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;

}

Solution

  • 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.