Consider the following code:
void f(void) {
unsigned char x[1]; /* intentionally uninitialized */
x[0] ^= x[0];
printf("%d\n", x[0]);
printf("%d\n", x[0]);
return;
}In this example, the
unsigned char
arrayx
is intentionally uninitialized but cannot contain a trap representation because it has a character type. Consequently, the value is both indeterminate and an unspecified value. The bitwise exclusive OR operation, which would produce a zero on an initialized value, will produce an indeterminate result, which may or may not be zero. An optimizing compiler has the license to remove this code because it has undefined behavior. The twoprintf
calls exhibit undefined behavior and, consequently, might do anything, including printing two different values forx[0]
.
The programmer has clearly attempted to set x[0]=0.
The compiler here is claimed to be “optimizing” by removing the xor. So what is exactly made faster or more efficient by this “optimization” ? (Note that there is no “volatile” used on the variable, so the compiler is permitted to optimize using the result of the first read.) Let me ask an easier question: is there an example of a program in which such an “optimization” results in a better executable by some sensible measure? If all we care about is execution speed, without regard to program correctness, compilers could optimize by generating nothing at all. Wow, that null program sure runs fast!
Some compiler writers would prefer to eliminate trap representations altogether and simply make any uninitialized read undefined behavior—the theory being, why prevent compiler optimizations because of obviously broken code? The counter argument is, why optimize obviously broken code and not simply issue a fatal diagnostic?
Why optimize obviously broken code? Why declare a working and common usage in C to be broken in the first place?
According to the current WG14 Convener, David Keaton, reading an indeterminate value of any storage duration is implicit undefined behavior in C, and the description in Annex J.2 (which is non-normative) is incomplete. This revised definition of the undefined behavior might be stated as “The value of an object is read while it is indeterminate.”
And yet:
Uninitialized memory has been used as a source of entropy to seed random number generators in OpenSSL, DragonFly BSD, OpenBSD, and elsewhere.10 If accessing an indeterminate value is undefined behavior, however, compilers may optimize out these expressions, resulting in predictable values.1
So the intent of at least some members of the C standard committee is to make production C code fail in some unpredictable manner as an “optimization”. Despite the best efforts of developers of rival programming languages, C’s advantages have preserved it as an indispensable systems and applications programming language. Whether it can survive the C standards process is a different question.
As a final note: this type of design failure is not all that unusual, but it’s the job of engineering management to block it. The proposal to silently make working crypto code fail in order to enable some type of efficiency for the compiler should be a non-starter.
Thanks to John Regehr for bringing this report from the weird underworld of C standard development to my attention. Also this