Question

Sequence Points and Undefined Behavior in C and C++

cppundefined-behaviorc++-faqsequence-points

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:

  • A sequence point is a place where the language guarantees ordering.
  • If two operations on the same object are not properly ordered, your expression may have undefined behavior.

Classic examples

Safe

i++;
int x = i;

These happen in separate statements, so the increment is complete before x is assigned.

Unsafe

int x = i++ + i;

This reads i and modifies i in the same expression without a guaranteed sequencing relationship.

Safer rewrite

++i;
a[i] = i;

Now the increment happens first, then the assignment happens clearly in a later statement.

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.

Take Quiz

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.

Places that historically act as sequence points in C/C++

Common examples include:

  • The end of a full expression, such as at the ;
  • After evaluation of all function arguments and the function designator, before entering the function
  • The logical && operator
  • The logical || operator
  • The conditional operator ?: between the first operand and the chosen second or third operand
  • The comma operator , when used as the comma operator, not just as a separator

Example using &&

if (i > 0 && ++i < 10) {
    // left side is evaluated first
    // right side is only evaluated if needed
}

Example using the comma operator

int x = (i++, i);

Here the left part i++ is completed before the right part i is evaluated.

Best practice

Write code for humans first:

++i;
a[i] = i;

That is better than:

a[++i] = i;

Step by Step Execution

Consider this safe example:

int i = 2;
++i;
int value = i;

Step by step:

  1. i starts as 2.
  2. ++i; increments i to 3.
  3. The statement ends, so that work is complete.
  4. int value = i; reads i, which is now clearly 3.
  5. value becomes 3.

Now compare that to this expression:

int i = 2;
int value = ++i + i;

What should happen first?

  • Should ++i run first, making i become 3, then the other i is read as 3?
  • Or is the other i read before the increment is applied?

If the language does not guarantee the needed order, the compiler is free to evaluate parts of the expression in a way that makes the program undefined.

Now look at the original style of example:

int i = 1;
a[++i] = i;

Possible questions the compiler must resolve:

  1. When is i incremented?
  2. When is the array index computed?
  3. When is the right-hand i read?
  4. Is the read of i guaranteed to happen before or after the increment?

Because the expression modifies i and also reads i without proper sequencing, the expression has undefined behavior.

Clear version

++i;
a[i] = i;

This version is easy to trace:

  1. Increment i
  2. Use the new value as the index
  3. Store the same new value into that array position

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.
Take Quiz

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

If you need to stop and think about evaluation order, rewrite the code.

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:

  • i is modified by i++
  • i is 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:

  • i is modified twice in one expression without proper sequencing

Safer version:

int i = 0;
++i;
int left = i;
++i;
int x = left + i;

3. Assuming compilers evaluate left to right

Broken assumption:

foo(i++, i);

A beginner may expect the first argument to always be evaluated before the second. That is not a safe assumption in general C/C++ reasoning.

Safer version:

int first = i;
++i;
foo(first, i);

4. Confusing commas with the comma operator

This is not the same thing:

foo(a, b, c);   // commas separate function arguments

versus:

int x = (a++, a);  // comma operator

Only the comma operator introduces sequencing here.

5. Writing code that is technically legal but hard to read

Even when an expression is valid, overly compact code can confuse readers.

Less readable:

result = data[index++] * factor;

Often clearer:

result = data[index] * factor;
++index;

Readability is an important practical reason to avoid convoluted expressions.

Comparisons

Sequence points vs undefined behavior

ConceptMeaning
Sequence pointA place where earlier side effects are complete before later evaluation continues
Undefined behaviorThe language gives no guarantees about what happens

Old terminology vs modern terminology

TermMeaning
Sequence pointOlder C/C++ way to talk about guaranteed ordering points
Sequenced before / afterModern C++ wording for ordering relationships between evaluations
UnsequencedNo guaranteed order between evaluations

Safe vs unsafe expression style

StyleExampleRecommendation
Clear separate steps++i; a[i] = i;Preferred
Compact but riskya[++i] = i;Avoid
Multiple side effects in one expressionx = i++ + ++i;Avoid

Sequence point related operators

Operator / constructOrdering help
; end of statementEnds a full expression
&&Left operand is evaluated before right operand if needed
`
?:First operand is evaluated before the chosen branch
comma operatorLeft side is evaluated before right side

Practical comparison

ApproachBenefitDrawback
One clever expressionShorter codeHarder to reason about, may be undefined
Several simple statementsPredictable and readableSlightly more verbose
Take Quiz

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:

  1. compute,
  2. store,
  3. update,
  4. use.

One-line memory rule

Clever expressions are rarely worth the risk in C and C++.

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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more