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).
#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.
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.