Greg said:
The idea is to allow C to run on a diversity of systems
and furthermore to be optimized when possible. So the
"loose" rules allow that, while the undefined behavior
allows your possibilty as oneof the undefined choices.
Is this annoying? Seems to me be. However, the other
side of the coin is that C would probably not be as
popular w/o it. Some may say this is a good thing.
A compiler can reorder the evaluation of an expression arbitrarily, as
long as any necessary side effects are produced, and the outcome is
correct.
Defining the order of evaluation does not hinder this freedom, except
in cases when there is insufficient compile-time info.
That is to say, whenever you write an expression that /clearly/ does
not violate the rules (modify something twice, or read something that
is modified in a way that does not compute the new value), it doesn't
make any difference whether the evaluation order is unspecified, or
whether it's strict left to right. By clearly, I mean that it's
statically analyzable.
There are cases whose defined-ness is conditional on run-time values.
(*x) = (*y)++;
The above is undefined if x and y point to the same object, otherwise
it is well-defined. It may be impractical, or impossible to determine
this statically. The rules of C and C++ allow the compiler the freedom
to optimize without analyzing this at all. Since the behavior is
undefined if x == y, the compiler doesn't have to care about that;
undefined behavior is the programmer's problem. It can assume that *x
and *y are distinct objects, and generate the target code in any of the
ways which are correct under that assumption.
Under strict left to right evaluation, the compiler cannot do that. It
must pessimistically assume that x and y could be aliased. The
semantics goes something like this: *x must be evaluated first to
produce a stable lvalue which is remembered in some temporary location.
Then, *y is incremented, and its old value goes to the location
remembered in the temporary.
So basically the design rationale for expression evaluation in C and
C++ is this: that (1) the risky possibility that ambiguous evaluation
may lead to less reliable software is /worth/ whatever benefit provided
by the freedom to make the optimistic assumptions with regard to
aliasing, and moreover, (2) that this is a better design than
restricting evaluation order, and instead providing a feature whereby
the programmer can declare that specific objects are distinct.
But of course, with regard to (2), we do now have that declaration
mechanism in C99 (albeit not in C++). Since there is a way to declare
that x and y point to distinct objects, there is no more need for
ambiguous evaluation orders. You can just write:
decl spec ... * restrict x, * restrict y;
// ...
(*x) = (*y)++;
and this will now be undefined if x and y are aliased, /even if/ we pin
the evaluation order to be strict left to right!!! With near pinpoint
precision, the programmer can get the undefined behavior in there,
undefined behavior which occurs exactly in the same case as when
evaluation order is left unspecified, without the whole language having
to have unspecified evaluation order everywhere!
What are the remaining optimization opportunities that are not
addressed by restrict pointers, but critically depend on loose
evaluation order?