Uninitialized Variables: An Example of Silent Miscompilation

Martin Uecker, 2026-10-17

As discussed last year for two other examples, some C compilers got much better in diagnosing undefined behavior (UB). Unfortunately this is not always the case. Here is another example (adapted from this bug report).

Example 1 (Godbolt)

#if __clang__
[[clang::noinline]]
#else
[[gnu::noinline]]
#endif
int foo(int i, int x)
{
    return i ? x + 1 : 0;
}

int main()
{
    int x;

    for (int i = 0; i < 10; i++)
        x = foo(i, x);

    return x;
}
	

Here, GCC will diagnose the use of the uninitialized variable with -Wuninitialized (part of -Wall) as long as optimization is turned on.


: In function 'main':
:17:13: warning: 'x' is used uninitialized [-Wuninitialized]
   17 |         x = foo(i, x);
      |             ^~~~~~~~~
:14:9: note: 'x' was declared here
   14 |     int x;
      |         ^
	

Clang, on the other hand, will aggressively and silently optimize the code producing an empty main function. To be fair, it can produce a warning with the flag -Wconditional-uninitialized, but this flag is neither part of -Wall nor -Wextra, because it produces too many false positivies. The underlying reason is that Clang has a very aggressive optimizer, but warnings are only produced in the compiler frontend based on a superficial analysis of the code. This leads to an unfortunate mismatch between aggressive optimization on the one hand and weak warnings on the other hand. When removing the noinline, GCC will also stop diagnosing the issue (but also still not miscompile the code), which shows that warning in later compiler passes also is not trivial, as the connection to the source code is partially lost, which can result in both false negative and false positive warnings.

Uninitialized Reads are Generally Not Undefined Behavior in C

Another important aspect is that the C standard makes this undefined behavior only when the address of the variable is not taken. It is a common misconception that reading uninitialized variables is always undefined behavior in C. It is only in two specfic situations. The first is for automatic variables whose address was not taken. In fact, this case of undefined behavior was not added to support optimization, but added specifically as a safety feature. It was added to be able to detect reading uninitialized behavior on Itanium processors, which could trap when reading an uninitialized register. (And this would also be suitable for a checking in software as tracking the state locally should be efficient, but I am not aware of any compiler offering this feature.) The other situation is if the type has non-value representations and the indeterminate representation of the uninitialized object happens to correspond to such a non-value representation (and the compiler may even assume this as along as the representation bytes are not directly observed by the program). Hence, the following example does not have undefined behavior according to ISO C on platforms where int has no non-value representations, but still is miscompiled by Clang. I filed a bug here.

Example 2

#if __clang__
[[clang::noinline]]
#else
[[gnu::noinline]]
#endif
int foo(int i, int x)
{
    return i ? x + 1 : 0;
}

int main()
{
    int x;

    (void)&x;

    for (int i = 0; i < 10; i++)
        x = foo(i, x);

    return x;
}
	

At least when actually passing the address of the variable, none of the compilers miscompiles the code in the examples I tried, and I assume that this would also break a lot of existing C code.

Example 3 (Godbolt)

int foo(int i, int *p)
{
    int x = *p;
    return i ? x + 1 : 0;
}

int main()
{
    int x;

    for (int i = 0; i < 10; i++)
        x = foo(i, &x);

    return x;
}
	

Conclusion

While optimizing C compilers historically often prioritized performance over safety, this generally got better over time and today we have much better warnings as well as many tools such as sanitizers and analyzers to find bugs. Unfortunately, there are still many issues left on both GCC and Clang. The mix of very aggressive optimization and comparable weak warnings make this especially problematic, and this affects Clang even more than GCC as it produces warnings only in the frontend where it is not possible to detect many issues. Also worrying is that compilers occasionally simply ignore (or very creatively interpret) the C standard where the standard already restricts compiler optimizations. To be safe, I recommend to use Clang for C programming only with -ftrivial-auto-var-init=zero and -fno-strict-aliasing as it is not conforming to ISO C without these flags. (I plan to write about the conformance issues with respect to aliasing next time.) GCC is generally safer and better conforming in my opinion, but there exist still similar issues in GCC as well.