This is a controversial topic
The C89 standard, section 3.16, defines:
undefined behavior: Behavior, upon use of a nonportable or erroneous
program construct, or of erroneous data, or of indeterminately-valued
objects, for which this International Standard imposes no requirements.
Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
And the 1999 C Rationale explains why:
Undefined behavior gives the implementor license not to catch certain
program errors that are difficult to diagnose. It also identifies
areas of possible conforming language extension: the implementor may
augment the language by providing a definition of the officially
undefined behavior.
The C99 standard, section 3.4.3, words it slightly differently, turning the second sentence into an explanatory note:
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
EXAMPLE An example of undefined behavior is the behavior on integer overflow.
Note that in the second sentence, the word "permissible" was changed to the word "possible".
This has resulted in two schools of thought in the C and C++ community.
The first group, largely made of C programmers, argues that the second sentence in the C89 standard was normative: it described the set of permissible behaviours. So "undefined behaviour" is only "undefined" in the sense that the standard does not require which of the permissible behaviours an implementation may do.
The second group, largely made of "standards lawyers" and open source C compiler implementors, point out that, under ISO rules, moving the second sentence to an explanatory note and changing the word "permissible" to "possible" means that it is not normative. These are merely possible behaviours, but because the standard imposes no requirements, any behaviour is possible.
This is also known as a "nasal demon", because the compiler making demons fly out of your nose is also a possible behaviour.
This is a problem for many C programmers, since it meant that WG14 declared a lot of customary C usage to be undefined behaviour. Chris Lattner of LLVM put it this way: "huge bodies of C code are land mines just waiting to explode."
So, for example, if you ever write this:
i << 32
If i
is a 32-bit integer, shifting it left by 32 is undefined behaviour. According to the second interpretation, any instance of this anywhere on an execution path renders the whole program semantically meaningless. And specifically, a compiler may assume that undefined behaviour can never happen and optimise your code accordingly.
This brings us to SPECint, the standard suite of integer benchmarks maintained by the Standard Performance Evaluation Corporation. This set of benchmarks is how C compiler vendors evaluate and market their code-generation and optimisation performance.
One of the benchmarks is 464.h264ref, which is a reference implementation of H.264/AVC video coding. It contains the following interestingly-written function:
int d[16];
int SATD (void)
{
int satd = 0, dd, k;
for (dd=d[k=0]; k<16; dd=d[++k]) {
satd += (dd < 0 ? -dd : dd);
}
return satd;
}
Please ignore the code quality for a moment. What I want you to notice is the for loop, specifically dd=d[++k]
. On the last iteration of the loop, this reads one element past the end of the array. The value is never used, so the read is actually safe. Nonetheless, this is technically undefined behaviour.
Apparently nobody noticed until a pre-release version of GCC 4.8 optimised it. The compiler reasoned:
- The out-of-bounds access of
d
cannot possibly happen.
- Therefore
k
cannot be greater than 15 before the array access d[++k]
.
- Therefore the loop test
k<16
is always true.
- Therefore this is an infinite loop.
- Therefore all the other code in this function is dead code.
And the final generated code was this:
SATD:
.L2:
jmp .L2
It was fixed before GCC was released, and it now gives a warning. Let it also be a warning to you.