The actual order in which expressions are evaluated is not specified for most of the operators in C. Because this sequence of evaluation is determined within the compiler depending on context, some unexpected results may occur when using certain operators. These unexpected results are caused by side effects.
Any operation that affects an operand's storage has a side effect. Side effects can be deliberately induced by the programmer to produce a desired result; in fact, the assignment operator depends on the side effect of altered storage to do its job. C guarantees that all side effects of a given expression will be completed by the next sequence point in the program. Sequence points are checkpoints in the program at which the compiler ensures that operations in an expression are concluded.
The most important sequence point is the semicolon marking the end of a statement. All expressions and their side effects are completely evaluated when the semicolon is reached. Other sequence points are as follows:
&&
expr2 (the logical
AND operator)
||
expr2 (the logical
OR operator)
These operations do guarantee the order, or sequence, of evaluation (expr1), expr2, and expr3 are expressions). For each of these operators, the evaluation of expression expr1 is guaranteed to occur before the evaluation of expression expr2 (or expr3, in the case of the conditional expression).
Relying on the execution order of side effects, when none is guaranteed, is a risky practice because results are inconsistent and not portable. Undesirable side effects usually occur when the same data object is used in two or more places in the same expression, where at least one use produces a side effect. For example, the following code fragment produces inconsistent results because the order of evaluation of operands to the assignment operator is undefined.
int x[4] = { 0, 0, 0, 0 }; int i = 1; x[i] = i++;
If the increment of i
occurs before the subscript is
evaluated, the value of x[2]
is 1. If the subscript is
evaluated first, the value of x[1]
is 1.
A function call also has side effects. In the following example, the
order in which f1(y)
and f2(z)
are called
is undefined:
int y = 0; int z = 0; int x = 0; int f1(int s) { printf ("Now in f1\n"); y += 7; /* Storage of y affected */ return y; } int f2(int t) { printf ("Now in f2\n"); z += 3; /* Storage of z affected */ return z; } main () { x = f1(y) + f2(z); /* Undefined calling order */ }
The printf
functions can be executed in any order even
though the value of x
will always be 10.