Kaz Kylheku said:
The reason is myopia: making it easy to write optimizing compilers,
at the cost of introducing risk into the code-base written in the programming
language.
Undefined orders of execution have no place in an imperative programming
language; i.e. one in which the majority of programs that are considered
idiomatic do their job by means of side effects in expressions.
If you were designing a C-like language from scratch today, and left
evaluation order undefined, you should be shot.
The belief that it provides speed is not a fact.
Even if evaluation order is well-defined, compilers can still rearrange the
evaluation, provided that the result of the computation is correct.
Only in cases where there are side effects does the order have to be
sufficienty constrained so that the effects are done in the proper order.
Note that C does define the order among full expressions, with the concept of
sequencing.
Any compiler that literally obeys sequence points cannot be called optimizing
by modern standards. A quality compiler must reorder computation across
sequence points! So if you write
x = a + b;
y = a + z;
even though there is a sequence point between these expressions,
they can be reordered, because they are independent. Even
these could be significantly rearranged:
a = i++;
b = i++;
The generated code could do something like
a = i;
b = i + 1;
i += 2;
Sequencing doesn't mean that the computation must literally take place as
written in every detail; it's an abstract concept tied to the ``as if'' rule.
Even if it can be shown than unspecified order of evaluation provides an
undeniable performance benefit, there is no reason why that order has to be
unspecified in every part of the program, in every expression in every function
in every source file.
Apparently several people are of the opinion that having language
semantics be more deterministic is better than being not as
deterministic, because... well, just because. To that I say,
just 'tisn't so.
Even if a clever optimizing compiler could recover (relative to C as
it is now) all the possible parallelism of a C-like language with a
defined left-to-right order of evaluation (and it can't, but never
mind that now), it still isn't automatically better to impose a
left-to-right evaluation order, or any fixed evaluation order. In
fact, doing that to C expressions would make C a worse language,
not a better one. Here's why.
If I see a piece of C code like, for example,
a = f( g(x), h(y) );
then I don't have to look at the definitions for g() and h() to know
they don't interact (by which I mean, interact in any nontrivial
way). The reason? If they did interact, the code would have been
written differently, to force a particular order of evaluation.
Of course, technically I don't know that g() and h() don't interact;
I know only that the person who wrote the code didn't think it was
important to force a particular order of evaluation. But knowing
the intent (or in this case, the lack of intent) of the code's
author is just as valuable here, or perhaps more valuable. I can
always go look at the definitions for g() and h() to see if they
interact, but I can't go back and look at what the code's author
was thinking.
Now consider the case where the language specifies a left-to-right
evaluation order. Let's look again at the example line. Now I have
to wonder if g() and h() interact; to find out I have to go read
their definitions. If they don't interact, I can breathe a big sigh
of relief and go back to reading the function where they were
called. But suppose they do interact; in that case I have to
wonder if the interaction was known and deliberate, or whether it
might have been unintentional. Short of going back and asking the
original author, there really isn't any way of knowing. Discovering
what the program does and what the program was intended to do has
become a lot more work.
Conversely, if I discover that g() and h() interact in C as it
is now, it's simply an error. The original programmer either
forgot something, or misunderstood something, or was confused
about something, or whatever; which ever of these is these is
the case, the program is simply in error, and I don't have to
wonder whether the interaction was intended or not -- it wasn't.[1]
Requiring a left-to-right evaluation order has made the job of code
reading harder rather than easier. And it encourages, even if only
indirectly, the writing of non-obvious code that depends on that
evaluation order. Both of those trends are trending in the wrong
direction.
[1] Of course, it's possible to construct situations where g() and
h() interact in a non-trivial way, yet the overall program behavior
is correct no matter which order is chosen. But the vast majority
of cases are not this way; moreover, any programmer who writes such
code without putting in a comment on that aspect deserves no better
treatment than a programmer who made an outright error, or at the
very least should be prepared for some sharp criticism during code
review.
If unspecified evaluation order is indeed an optimization tool, then it should
be recognized that it's a dangerous optimization tool, and a way should be
provided for the programmer to choose the regions of the program where the
order is unspecified. Suppose you had a reorder operator:
reorder /expression/
Everything under the reorder operator is subject to unspecified evaluation
order, other than sequencing operators. The return value and type of the
reorder operator are those of the expression.
There are two problems with this approach, one mechanical, one
behavioral.
The mechanical problem is that most expressions are reorderable, so
'reorder' would end up being used in almost every instance. That's
the wrong side of the 80/20 rule (and probably closer to 90/10 for
the situation of which expressions can be reordered).
The behavioral problem is that, because it's more work, most
programmers won't ever put in the 'reorder' directives, or will put
them in only very sparingly. But that's the opposite of what's good
in terms of reading the code -- the order /should/ be unspecified
unless there is a deliberate effort to do something different, so
that cases where the order matters stand out. If it's more work to
put in the option to reorder, in many cases that won't be done
simply out of laziness, which leaves us wondering in the cases where
the 'reorder' option wasn't specified whether that was deliberate or
not.
Futhermore, there already is a perfectly good way to force a
particular evaluation order if/when that is desired. In fact, two
ways -- assignment statements, and the comma operator. And, not
only can these be used to force an evaluation order, they offer
the choice of which evaluation order makes the most sense in
any individual circumstance.
Certainly the C language has some areas of semantic weakness. But
unspecified order of expression evaluation isn't one of them.