A Detailed Explanation of Sequence Points in C Language

A Detailed Explanation of Sequence Points in C Language

Author: Luo Guangxuan

In the C language, a sequence point is a “time point” in the execution process of a program, with the core rule being: all side effects of expressions (such as variable modifications) must be completely executed before the sequence point; new side effects can only begin after the sequence point.

The existence of sequence points is to clarify the “boundaries” of expression execution, avoiding uncertainty in behavior caused by compiler optimizations. Without sequence points, multiple modifications or read/write operations on the same variable within the same expression may lead to undefined behavior— that is, the C standard does not specify the exact execution order, and different compilers (or even different optimization levels of the same compiler) may produce completely different results.

1. Common Sequence Points

Sequence points explicitly defined in C include (but are not limited to):

1. Semicolon ;: At the end of a statement (for example, in a = 1;, the ; is a sequence point).

2. Comma operator ,: Separates expressions (for example, in a=1, b=2;, the , is a sequence point, ensuring that the side effect of a=1 is completed before executing b=2).

3. Logical AND && and logical OR ||: The position of short-circuit evaluation (for example, in expr1 && expr2, the && is a sequence point, and expr1 must be executed and determined to be true before executing expr2).

4. Conditional operator ?:: At the question mark and colon (for example, in expr1 ? expr2 : expr3, both ? and : are sequence points).

5. Function calls: After all actual parameters are evaluated and before the function body is executed (for example, in func(a, b), there is a sequence point after evaluating a and b, before entering the execution of func).

2. Example Analysis: Undefined Behavior Due to Missing Sequence Points

Example 1: Modifying the Same Variable Multiple Times in the Same Expression (No Sequence Point)

#include int main(){ int i =1; int result = i+++ i++; // Undefined behavior: No sequence point between two i++ printf(“result = %d, i = %d\n“, result, i); return0;}

Problem Analysis

·The expression i++ + i++ contains two i++(the two increment side effects on i), but there is no sequence point between them(+ is not a sequence point).

·The C standard does not specify the execution order of the two i++: whether to calculate the left one first or the right one first?

Different Compiler Results

·In GCC 9.4, the output may be result = 2, i = 3(first take i=1 to calculate the left side,i becomes 2; then take i=2 to calculate the right side,i becomes 3, total 1+2=3? Actual tests may vary due to different optimizations).

·In Clang 10, the output may be result = 3, i = 3(first take two i=1 to calculate the total of 2, then increment i to 3?).

·Other results may even occur—because the behavior is undefined, the compiler can handle it freely.

Correct Writing (using sequence points to separate): By splitting the expression with semicolons (sequence points), the order of side effects is clarified:

int i =1;int a = i++; // Semicolon is a sequence point, ensuring i++ completes (i becomes 2)int b = i++; // Now i=2, after increment i=3int result = a + b; // 1 + 2 = 3, result is determined

Example 2: Sequence Points in Logical AND && (Predictable Behavior)

#include int main(){ int i =0; // && is a sequence point: the left i++ executes first (side effect completes) before executing the right side int result =(i++&& i++); printf(“result = %d, i = %d\n“, result, i); return0;}

Execution Analysis:

·&& is a sequence point, the left expression i++ executes first: takes i=0 (logical is false), then i increments to 1.

·Since the left is false, && triggers short-circuit evaluation, the right i++ does not execute.

·Finally, result=0, i=1— the result is completely determined, with no ambiguity.

Example 3: Sequence Points in Function Arguments (Argument Order Undefined)

#include void func(int x, int y){ printf(“x = %d, y = %d\n“, x, y);}int main(){ int i =1; func(i++, i++); // Undefined behavior: The order of evaluation of actual parameters is not specified return0;}

Problem Analysis:

·When calling a function, there is a sequence point after “all actual parameters are evaluated”, but the order of evaluation between actual parameters is undefined (the C standard does not specify whether the first parameter or the second is calculated first).

·The expression func(i++, i++) has an uncertain execution order: whether to calculate the first i++ first or the second?

Different Compiler Results

·GCC may output x=1, y=2(first calculate the first parameter: i=1 is passed,i becomes 2; then calculate the second parameter: i=2 is passed,i becomes 3).

·Clang may output x=2, y=1(first calculate the second parameter: i=1 is passed,i becomes 2; then calculate the first parameter: i=2 is passed,i becomes 3).

Correct Writing (using sequence points to separate actual parameters): :

int i =1;int a = i++; // Semicolon is a sequence point, i becomes 2int b = i++; // i becomes 3func(a, b); // Pass in 1 and 2, result is determined

Example 4: Assignment Operator is Not a Sequence Point (Leading to Undefined Behavior)

#include int main(){ int i =1; i = i++; // Undefined behavior: Assignment is not a sequence point printf(“i = %d\n“, i); return0;}

Problem Analysis:

·The assignment operator = is not a sequence point, the expression i = i++ contains a “read” (the value of the right i ) and a “modify” (the increment of i++), and the order of both is undefined.

·Possible Execution Orders:

1.First read i=1 and assign it to i (i remains 1), then execute i++(i becomes 2).

2.First execute i++ (i becomes 2), then read i=2 and assign it to i (i remains 2).

Different Compiler Results:

·GCC may output i=2, Clang may output i=1— behavior is completely unpredictable.

Correct Writing:

int i =1;i++; // Semicolon is a sequence point, i becomes 2// or: i += 1; (compound assignment, behavior is determined)

3. Conclusion: The Core Significance of Sequence Points

1.Clarifying the Boundaries of Side Effects: Sequence points ensure that all variable modifications (side effects) before them are fully effective, avoiding “read/write conflicts”.

2.Avoiding Undefined Behavior: Multiple modifications or reads/writes of the same variable within the same expression must be separated by sequence points (such as semicolons, &&, commas), otherwise the result is unpredictable.

3.Compiler Optimization’s “Safe Zone”: Sequence points provide the compiler with optimization space (it can adjust the order of expressions without sequence points), but also require programmers to actively avoid undefined behavior.

The simplest principle in actual programming is: do not modify the same variable multiple times in the same expression, and do not read and write the same variable simultaneously without sequence points separating them. By splitting expressions (using semicolons), behavior can be ensured to be completely determined.

Leave a Comment