Sequence Points
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;
}
On my system, the output was 5 6 5 with GCC and 6
6 6 with the C compiler that came with Microsoft Visual
Studio. The versions of the compilers with which I got these
results are:
- gcc (Debian 4.3.2-1.1) 4.3.2
- Microsoft Visual Studio 2005 32-Bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
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 these
compiler versions.
The behaviour of such C programs is undefined. Consider the following two statements:
printf("%d %d %d\n", i, i--, ++i);a += a++ + a++;
We will see below that in both statements, the variable is modified more than once between two consecutive sequence 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 likex = f() + g();
fmay be evaluated beforegor vice versa; thus if eitherforgalters a variable on which the other depends,xcan depend on the order of evaluation. Intermediate results can be stored in temporary variables to ensure a particular sequence.
Note that the comma (,) mentioned here is the comma
operator, not the comma that separates function arguments. 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
nis incremented beforepoweris 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
ior 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 there
is no sequence point between the evaluations of the individual
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 (iforswitch) (6.8.4); the controlling expression of awhileordostatement (6.8.5); each of the expressions of aforstatement (6.8.5.3); the expression in areturnstatement (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 more than
once before the sequence point. Thus the behaviour of this
statement is undefined.