Question
Sequence Points and Undefined Behavior in C and C++
Question
In C and C++, what are sequence points, and how are they related to undefined behavior?
For example, why are compact but confusing expressions such as the following considered problematic?
a[++i] = i;
Why should code like this be avoided, even if it sometimes appears to work?
Short Answer
By the end of this page, you will understand what sequence points are, why expression evaluation order matters, and how modifying and reading the same variable without proper sequencing can cause undefined behavior in C and C++. You will also learn safer ways to write these expressions so your code is predictable and maintainable.
Concept
In C and older C++ terminology, a sequence point is a point in program execution where all side effects of previous evaluations are guaranteed to be complete before the next evaluations begin.
A side effect includes things like:
- Changing the value of a variable
- Incrementing or decrementing a variable
- Writing to memory
- Calling a function that changes state
The important idea is this:
- If you modify a variable and also read or modify that same variable again in the same expression
- and the language does not guarantee an order between those actions,
- the result can be undefined behavior.
For example:
i = i++;
or
a[++i] = i;
These are problematic because the program depends on the order in which parts of the expression are evaluated, but that order is not guaranteed in the required way.
Why this matters
When behavior is undefined, the language standard places no requirements on what happens. That means:
- the code may appear to work,
- may produce different results on another compiler,
- may break with optimization enabled,
- or may behave inconsistently across runs.
This is one of the reasons experienced C and C++ developers avoid clever one-line expressions that both update and use the same variable.
Modern wording in C++
In modern C++, the standard more often talks about expressions being sequenced before, sequenced after, or unsequenced, instead of focusing only on the older term sequence point.
But for learning purposes, the older idea is still useful:
Mental Model
Think of an expression like a group of workers trying to update the same whiteboard.
- A sequence point is like a supervisor saying: "Stop. Everyone finish your current work before the next step starts."
- Without that stop point, two workers may try to read and erase the same note at the same time.
For example, in this expression:
a[++i] = i;
one part is trying to increment i, while another part is reading i. If the language does not say which happens first, you are relying on chaos.
A simpler rule to remember:
If one expression both changes a variable and also uses that same variable again, split it into separate statements unless the order is explicitly guaranteed.
Syntax and Examples
Core idea
Avoid expressions where the same variable is both:
- modified more than once, or
- modified and read for another purpose,
without a guaranteed sequencing relationship.
Problematic examples
int i = 1;
int x = i++ + i; // undefined behavior
int i = 1;
i = ++i + 1; // undefined behavior in older rules / unsafe style to avoid
int i = 1;
a[++i] = i; // undefined behavior
Safe rewrites
Example 1: split the expression
int i = 1;
++i;
a[i] = i;
This is clear and predictable.
Example 2: store intermediate values
int i = 1;
int next = i + 1;
i = next;
a[i] = i;
This is more verbose, but very easy to reason about.
Step by Step Execution
Consider this safe example:
int i = 2;
++i;
int value = i;
Step by step:
istarts as2.++i;incrementsito3.- The statement ends, so that work is complete.
int value = i;readsi, which is now clearly3.valuebecomes3.
Now compare that to this expression:
int i = 2;
int value = ++i + i;
What should happen first?
- Should
++irun first, makingibecome3, then the otheriis read as ?
Real World Use Cases
This concept matters anywhere evaluation order can affect program correctness.
Common real scenarios
Updating counters
count++;
logCount(count);
This is clearer and safer than combining counter updates and logging in one expression.
Array or vector indexing
When working with buffers, strings, or vectors, developers often update an index and use it immediately. Splitting the steps prevents subtle bugs.
++index;
buffer[index] = value;
Parsing input
While scanning characters or tokens, it may be tempting to write compact code that increments a position and reads from it in one expression. Clear sequencing makes parser code easier to debug.
Function calls with side effects
If a function modifies state, avoid mixing that with other reads of the same state in one expression.
updateState();
printState();
Why teams care
In production code, undefined behavior can lead to:
- compiler-specific bugs,
- optimization-related failures,
- hard-to-reproduce issues,
- confusing code reviews,
- and maintenance problems years later.
Real Codebase Usage
In real projects, developers usually avoid relying on tricky expression ordering.
Common patterns
Guard clauses and simple steps
Instead of clever one-liners, codebases prefer short, explicit statements:
if (index + 1 >= size) {
return;
}
++index;
buffer[index] = value;
Validation before mutation
Read values first, validate them, then modify state.
int next = index + 1;
if (next < size) {
index = next;
buffer[index] = value;
}
Avoid side effects in expressions
Many style guides encourage keeping side effects out of conditions, arguments, and complex assignments.
Less preferred:
process(items[++i], i);
Preferred:
++i;
process(items[i], i);
Easier debugging
Separate statements make it easier to:
- inspect variables in a debugger,
- add logging,
- write tests,
- and review code safely.
Rule of thumb used in teams
Common Mistakes
1. Modifying and reading the same variable in one expression
Broken example:
int i = 0;
int x = i++ + i;
Why it is a problem:
iis modified byi++iis also read again in the same expression- the required ordering is not guaranteed
Safer version:
int i = 0;
int first = i;
++i;
int x = first + i;
2. Modifying the same variable twice
Broken example:
int i = 0;
int x = ++i + ++i;
Why it is a problem:
iis modified twice in one expression without proper sequencing
Safer version:
int i = 0;
++i;
int left = i;
++i;
int x = left + i;
Comparisons
Sequence points vs undefined behavior
| Concept | Meaning |
|---|---|
| Sequence point | A place where earlier side effects are complete before later evaluation continues |
| Undefined behavior | The language gives no guarantees about what happens |
Old terminology vs modern terminology
| Term | Meaning |
|---|---|
| Sequence point | Older C/C++ way to talk about guaranteed ordering points |
| Sequenced before / after | Modern C++ wording for ordering relationships between evaluations |
| Unsequenced | No guaranteed order between evaluations |
Safe vs unsafe expression style
| Style | Example | Recommendation |
|---|
Cheat Sheet
Quick rules
- Do not modify and read the same variable in one expression unless the language guarantees the order.
- Do not modify the same variable more than once in one expression unless sequencing guarantees make it safe.
- If unsure, split the expression into separate statements.
Common unsafe patterns
i = i++;
int x = i++ + i;
int y = ++i + ++i;
a[++i] = i;
Safer patterns
++i;
a[i] = i;
int old = i;
++i;
int x = old + i;
Classic sequence-point style reminders
Ordering is guaranteed at or through:
- end of a full expression
&&||?:- comma operator
- function call boundary after arguments are evaluated and before entering the function
Best habit
When side effects and value computation mix together, prefer:
- compute,
- store,
- update,
- use.
One-line memory rule
FAQ
What is a sequence point in C and C++?
A sequence point is a place where all earlier side effects are guaranteed to be complete before later evaluation continues. In modern C++, this idea is mostly described using sequencing rules such as "sequenced before".
Why is a[++i] = i; undefined behavior?
Because i is modified by ++i and also read again as i in the same expression without the necessary guaranteed ordering.
What does undefined behavior mean?
It means the language standard does not define the result. The program might appear to work, fail unexpectedly, or behave differently across compilers or optimization levels.
Is undefined behavior the same as a compiler error?
No. A compiler may compile code with undefined behavior without warning. The absence of an error does not make the code safe.
Are sequence points still used in modern C++?
The older term is still useful for learning, but modern C++ usually explains these rules with terms like "sequenced before", "indeterminately sequenced", and "unsequenced".
How can I avoid sequence point problems?
Use separate statements for updates and reads of the same variable. If an expression feels clever, rewrite it more clearly.
Does adding parentheses fix the problem?
Usually no. Parentheses change grouping, but they do not necessarily create a sequencing relationship.
Why do programmers avoid these expressions even if they seem to work?
Because they are hard to read, easy to misunderstand, and can break when compiled differently or maintained later.
Mini Project
Description
Build a small C++ program that demonstrates the difference between unclear side-effect-heavy expressions and clear, sequenced code. The goal is to practice rewriting risky expressions into readable statements that do the same job safely.
Goal
Write a program that updates an index and stores values in an array using clear, well-sequenced statements instead of undefined expressions.
Requirements
- Create an integer array with at least 5 elements.
- Use an index variable and update it step by step.
- Store values into the array without reading and modifying the same variable in one risky expression.
- Print the final array contents.
- Include at least one commented-out example of a bad expression that should be avoided.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Base Class Constructor Rules Explained
Learn how C++ base class constructors are called from derived classes, including order, syntax, defaults, and common mistakes.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.