C, Ocean Edition I

Annex E. Evaluation Order

Annex
E
ISO C99 mapping
Annex C, 5.1.2.3, 6.5
Status
Normative

E.0 What This Annex Replaces

ISO C99 Annex C lists the sequence points of the language. This annex replaces it, because Ocean Edition I has no sequence points.

A sequence point is a device for describing a language in which evaluation order is mostly unspecified. It answers the question "at which moments is the compiler's freedom constrained?" When the order is fixed, the question does not arise, and the machinery that answered it is not needed. See behavior.md section 104.


E.1 The Rule

Subexpressions are evaluated from left to right, unless the syntax of the construct specifies a different control dependency.

An effect of an evaluation takes place before the next evaluation in the order begins.

Those two sentences replace sequence points, unsequenced modification, indeterminately sequenced operations, and unspecified argument evaluation order.


E.2 Order by Construct

Construct Order
a op b, for any binary arithmetic, bitwise, relational, or equality operator a, then b, then the operation
f(a, b, c) f, then a, then b, then c, then the call
a[b] a, then b, then the subscript
a.m, a->m a, then the member access
a = b the lvalue a, then b, then the store
a op= b the lvalue a, then b, then the operation, then the store
a, b a, then discard, then b
++a, a++, --a, a-- the lvalue, then the read, then the modification
(type)a a, then the conversion
&a, *a, -a, ~a, !a the operand, then the operation
sizeof a the operand is not evaluated
{ x, y, z } x, then y, then z
a && b a; b only if a is true
a \|\| b a; b only if a is false
c ? a : b c, then exactly one of a or b
statement sequence each full expression completes before the next statement begins
for (i; c; s) body i, then c, then body, then s, then c, and so on
while (c) body c, then body, then c, and so on
do body while (c) body, then c, then body, and so on
switch (e) e once, then the transfer
return e e, then the lifetimes end, then the transfer
static initialization before program startup, in an order the program cannot observe

E.3 Worked Examples

Every expression below has exactly one result.

int i = 1;
int a[4];

i = i++;              /* i becomes 1: increment applies, then the old value is stored */
i = ++i;              /* i becomes 2: increment applies, then the new value is stored */
a[i++] = i;           /* the index is the old i; the value stored is the new i */
f(i++, i);            /* the first argument is the old i; the second is the new i */
x = f() + g();        /* f runs, then g */
x = f() * g() + h();  /* f, then g, then h */
p = p->next->next;    /* left to right through the chain */
int count(void)
{
	static int calls;
	calls++;
	return calls;
}

int total = count() + count() * count();

The calls happen in written order and return 1, 2, and 3. Precedence groups the expression as 1 + (2 * 3), so total is 7. The two rules are independent, and each is stated once. See clause 6.5.0.2.


E.4 Style Is Still Style

Defining the order does not make every expression a good expression.

i = i++;

now has a determined result, and it is still a line no reviewer should approve. An implementation should diagnose an expression that modifies an object more than once, or that both modifies and independently reads an object, within one full expression.

The change is that such an expression is a readability problem rather than a metaphysical one. A programmer can look at it, work out what it does, and then decide to rewrite it. See syntax.md section 27.


E.5 Implementation Notes

E.5.1 Normalization happens early

The frontend fixes evaluation order during semantic normalization, in translation phase 5. No later pass decides it.

For:

foo(a(), b(), c());

the frontend conceptually emits:

v0 = call a
v1 = call b
v2 = call c
call foo(v0, v1, v2)

Once the order is in the intermediate representation, every optimization pass inherits it for free, and no pass has to be taught about it. See syntax.md section 55.

E.5.2 Reordering is still permitted

An implementation may evaluate subexpressions in any physical order that produces the same observable result. Where two subexpressions have no effects and cannot trap, their order is unobservable, and the scheduler may do as it likes.

The cost of the rule is confined to expressions whose subexpressions have effects, can trap, or access volatile storage. In ordinary code that cost is close to zero, and in the code where it is not zero, the programmer wanted the order.

E.5.3 Lowering to C99

Deterministic order lowers to portable C99 by naming the intermediate values:

foo(a(), b());

becomes:

T0 _a = a();
T1 _b = b();

foo(_a, _b);

See annex-h-migration-and-interoperation.md.


E.6 What Does Not Have an Order

Two things remain outside this annex, and both are stated so that no reader infers a guarantee.

Initialization of objects with static storage duration. Every such object is initialized before program startup. Because each initializer is a constant expression, no program can observe the order in which they are performed.

Execution in two threads. Where an implementation provides threads, this annex describes each thread's own execution and says nothing about the interleaving of two. Clause 5.1.2.4 declines to define a concurrency model, and an implementation that provides threads shall document its memory model.