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;
}
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:
- 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 the
compilers.
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 the statements, the variable is modified twice 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();
f
may be evaluated beforeg
or vice versa; thus if eitherf
org
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 beforepower
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
orswitch
) (6.8.4); the controlling expression of awhile
ordo
statement (6.8.5); each of the expressions of afor
statement (6.8.5.3); the expression in areturn
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.