For example, your example could legally be rewritten to:
int x;
f();
x = 1;
if (g()) { h(x); }
The only difference is if f() were false, and someone would then access x and see 1; but that's undefined behavior, so you can ignore it. In fact, assuming x is not accessible outside this block: f();
if (g()) { h(1); }
These optimizations happen all over the place, it's not the compiler invoking or causing undefined behavior, but assuming that it won't ever happen.EDIT: Note the further optimization that looms: if f() can be proven pure (no side effects), then it can be removed. This makes little sense for a function with no arguments (in which case it would just be a constant). If, however, f(y, ...) is some expensive but pure function, it can just be removed completely.
It could apply if f computes a value based on global state but doesn't change anything, which is slightly weaker than "pure".
So changing that behavior means that the compiler now rejects 40 years worth of correctly working legacy code (and some buggy code, as well). Newer languages (e.g. Java, C#) that don't have to support existing code can afford to do what you want and reject programs where a simple heuristic isn't enough to tell whether a variable is initialized or not.
int x;
h(x);
The point is not to solve the Halting Problem, but to catch code that is so obviously wrong that no programmer would deliberately write it (unless they were testing the compiler's reaction.)On top of that, the C compilers I know more or less have that, as they give warnings for basically cases where Java (with its stricter rules) would refuse to compile equivalent code, and have a flag that turns warnings into errors.
Often there is no sane default value.
g() might have side-effects, and those side-effects have to happen regardless of whether or not f() returned true.
if (f()) {
int x = 1;
if (g()) {
h(x);
}
} else {
g();
}
If g() can be called before f() then it could also
be written as: int g_flag = g();
if (f()) {
int x = 1;
if (g_flag) {
h(x);
}
|
If you really only had one assignment to x,
and both f() and g() have side effects that
must be run in-order, this can be more
clearly written as: int f_flag = f(); // force f() to run for ${reasons}
int g_flag = g(); // force g() to run for ${reasons}
if (f_flag && g_flag) {
h(1);
}
This is a lot clearer about what the code is doing, and
the compiler is probably going to optimize away the two
int flag variables anyway.You should never rely on uinitialized values not just because of the problems relating to undefined behavior, but also because you're adding in assumptions about the runtime state. The code depended on the return value of f() but did not fully express that dependency in the code.
You cannot implement that efficiently without rejecting valid programs. Consider code like this:
For sufficiently complex functions f and g, there's no reasonable way to decide at compile time whether x will be set whenever g() returns true. For example, f() might always return false because Fermat's last theorem is true.And that 'reasonable' likely isn't a necessary part of that statement.