... here is what Harbison (C - A reference Manual) has to say
(7.12 Order of Evaluation).
The assumption of commutativity and associativity is
always true for &, ^, and | on unsigned operands...
It may not be true for * and + because of the possibility
that the order indicated by the expression as written
might avoid overflow but another order might not.
Nevertheless, the compiler is allowed to exploit the
assumption. In such situations the programmer must use
assignments to temporary variable to force a particular
evaluation order:
H&S is simply wrong here (or, more charitably, they are referring
to "K&R C", before the original ANSI C Standard, when the
specification for C was quite loose).
During the original 1980s debates on standardization, there were
some who argued that the compiler should be free to do the above
kinds of rearrangment, and for a while, there was a proposal that
the unary "+" operator (but not plain parentheses) would suppress
it. Thus:
int i, j, k, sum;
i = INT_MAX; /* e.g., 32767 */
j = -i; /* e.g., -32767 */
k = 3;
sum = (i + j) + k;
could overflow, because while (i+j) is obviously 0, and 0+3 obviously
does not overflow, the compiler might compute (i+k) first. To
suppress this, the proposal went, one would write instead:
sum = +(i + j) + k;
which would force the compiler to do the i+j operation as a unit,
resulting in 0, so that the overall operation must succeed.
Fortunately, sanity prevailed, and the X3J11 committee folks
eventually agreed that the compiler *must* do the operations
"as if" they happened as specified by the abstract semantics.
The actual wording reads:
In the abstract machine, all expressions are evaluated
as specified by the semantics.
The compiler is free to rearrange expressions (under the "as if"
rule) only if it can prove that the "programmer-visible effects"
are the same (where "programmer-visible" includes things like
runtime traps, but not things like runtime speed). If the compiler
knows that signed-integer overflow traps are disabled, for instance,
so that (i+k)+j produces the same answer as (i+j)+k, it can use
either order. If it knows that signed-integer overflow traps are
enabled, or if it cannot be sure[%], it should avoid the rearrangement.
[% The compiler usually has a lot of leeway in the "cannot be sure"
department since, at least most of the time, anything the programmer
does to affect a trap-on-overflow flag is not done with Standard
C. (I use this "most of the time" wording only because I have not
yet studied the C99 floating point models.) Because a non-Standard
operation can *always* affect the rest of the system in ways the
compiler need not appreciate, a compiler could go ahead and
rearrange things anyway, by simply *assuming* that you -- the
programmer -- will not turn on the trap-on-overflow flag. After
all, we all know it is most important to get the wrong answer as
fast as possible -- getting the right answer slower is never any
use at all.
]