Sequence Points

By Susam Pal on 26 May 2010

Code Examples

A particular type of question comes up often in C programming forums. Here is an example of such a question:

#include <stdio.h>

int main()
{
    int i = 5;
    printf("%d %d %d\n", i, i--, ++i);
    return 0;
}

The output is 5 6 5 when compiled with gcc and 6 6 6 when compiled with the C compiler that comes with Microsoft Visual Studio. The versions of the compilers with which I got these results are:

Here is another example of such a question:

#include <stdio.h>

int main()
{
    int a = 5;
    a += a++ + a++;
    printf("%d\n", a);
    return 0;
}

In this case, I got the output 17 with both the compilers.

The behaviour of such C programs is undefined. Consider the following two statements:

We will see below that in both the statements, the variable is modified twice between two consecutive points. If the value of a variable is modified more than once between two consecutive sequence points, the behaviour is undefined. Such code may behave differently when compiled with different compilers.

K&R

Before looking at the relevant sections of the C99 standard, let us see what the book The C Programming Language, Second Edition says about such C statements. In Section 2.12 (Precedence and Order of Evaluation) of the book, the authors write:

C, like most languages, does not specify the order in which the operands of an operator are evaluated. (The exceptions are &&, ||, ?:, and ','.) For example, in a statement like

x = f() + g();

f may be evaluated before g or vice versa; thus if either f or g alters a variable on which the other depends, x can depend on the order of evaluation. Intermediate results can be stored in temporary variables to ensure a particular sequence.

In the next paragraph, they write,

Similarly, the order in which function arguments are evaluated is not specified, so the statement

printf("%d %d\n", ++n, power(2, n));    /* WRONG */

can produce different results with different compilers, depending on whether n is incremented before power is called. The solution, of course, is to write

++n;
printf("%d %d\n", n, power(2, n));

They provide one more example in this section:

One unhappy situation is typified by the statement

a[i] = i++;

The question is whether the subscript is the old value of i or the new. Compilers can interpret this in different ways, and generate different answers depending on their interpretation.

C99

To read more about this, download the C99 standard, go to section 5.1.2.3 (Program execution), and see the second point which mentions:

Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects,11) which are changes in the state of the execution environment. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. (A summary of the sequence points is given in annex C.)

Then go to section 6.5 and see the second point which mentions:

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression.72) Furthermore, the prior value shall be read only to determine the value to be stored.73)

Finally go to Annex C (Sequence Points). It lists all the sequence points. For example, the following is mentioned as a sequence point:

The call to a function, after the arguments have been evaluated (6.5.2.2).

This means that in the statement

printf("%d %d %d\n", i, i--, ++i);

there is a sequence point after the evaluation of the three arguments (i, i--, and ++i) and before the printf() function is called. But none of the items specified in Annex C implies that there is a sequence point between the evaluation of the arguments. Yet the value of i is modified more than once during the evaluation of these arguments. This makes the behaviour of this statement undefined. Further, the value of i is being read not only for determining what it must be updated to but also for using as arguments to the printf() call. This also makes the behaviour of this code undefined.

Let us see another example of a sequence point from Annex C.

The end of a full expression: an initializer (6.7.8); the expression in an expression statement (6.8.3); the controlling expression of a selection statement (if or switch) (6.8.4); the controlling expression of a while or do statement (6.8.5); each of the expressions of a for statement (6.8.5.3); the expression in a return statement (6.8.6.4).

Therefore in the statement

a += a++ + a++;

there is a sequence point at the end of the complete expression (marked with a semicolon) but there is no other sequence point before it. Yet the value of a is modified twice before the sequence point. Thus the behaviour of this statement is undefined.