cinitializationlanguage-lawyervariadic-functionsc23

Is it legal to empty-initialize `va_list` with `{}` in C23 before calling `va_start()`?


TL;DR: is C23's universal initialization type var = {}; safe for all types, including standard opaque ones, such as va_list?


I have code that uses variable arguments. An older version of a static analyzer Coverity has started to report a dubious warning about uninitialized variable when processing code compiled with GCC15 that chooses C23 standard by default.

That compiler internally uses a new builtin __builtin_c23_va_start() to implement va_start() (instead of older __builtin_va_start()). Apparently, Coverity does not yet understand that this builtin is meant to initialize the passed variable.

E.g. in the following function:

#include <stdarg.h>
#include <stdio.h>

void warning(const char *file, unsigned line, const char *fmt, ...) {
        va_list va;
        va_start(va, fmt);
        fprintf(stderr, "warning %s:%u: ", file, line);
        vfprintf(stderr, fmt, va);
        va_end(va);
        fprintf(stderr, "\n");
}

The analyzer reports: Using uninitialized value "va" when calling "__builtin_c23_va_start". on the second line of warning().

Possible approaches to suppress such false positives are: 1) update Coverity to a newer version (currently in progress), 2) suppress individual warning sites by annotations in comments (does the thing but makes code messier).

I am considering a third alternative: use empty initalization at the same line where the va is defined. That is, to have code like this:

va_list va = {};
va_start(va, fmt);

Adding = {} makes Coverity stop complaining. But I am not sure if this is allowed for objects of opaque standard types such as va_list. va will be initialized by va_start() on the next line, so it should be OK to use it afterwards. But isn't it undefined behavior already at that point?


Solution

  • If it is truly opaque - an incomplete struct not visible to whoever includes the header - then you wouldn't be able to declare an object of it in the first place. So it can't be. Furthermore, C actually guarantees that it is not an opaque type:

    C23 7.16.1

    va_list
    which is a complete object type suitable for holding information needed by the macros...

    (There's also a non-normative note saying that passing around pointers to a va_list object is fine.)

    Meaning it is either an object defined in some internals of a header or it is a macro/typedef hiding a pointer. In either case you can initialize it to zero. va_list va={0}; is valid C for all types (no matter if aggregate/union/scalar etc).

    In case of "scalars", for example int x = {0};, this is fine, the braces are optional (C23 6.7.11). In case of pointers the zero becomes a null pointer constant. And so on.

    As for {} in C23 it is just "syntactic sugar" for {0}, as per the rules of default initialization (C23 6.7.11).