What are sequence points in C++ and what does undefined behavior have to do with it?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

The Debugger's Secret Weapon: What C++ Programmers Overlook

\n\n

Have you ever written something like i = i++ + ++i; and gotten different results on different compilers? Or caught a strange bug that only reproduced on the server but not on your laptop? Meet undefined behavior — one of the most insidious enemies of a C++ developer. And at the center of this chaos are sequence points.

\n\n

In this article, we'll break down what sequence points are, how they relate to the order of evaluation in C++, and — most importantly — how to avoid stepping on these rakes. The material is based on a classic Stack Overflow question that has garnered thousands of views. Let's go!

\n\n

What are sequence points?

\n\n

Imagine you are an orchestra conductor. Each musician (processor instruction) must enter at a strictly defined moment. A sequence point is a point in time when all previous "side effects" (variable changes, function calls) are guaranteed to be complete, and the next ones have not yet begun.

\n\n

In the C++ 98/03 standard, the term sequence point was used. In C++11, it was replaced with the stricter "sequenced before / sequenced after" relationships, but the essence remains: between two sequence points, the compiler has full right to reorder operations, as long as it doesn't change the result (according to the "as-if" rule).

\n\n

Simply put: if you try to modify the same variable twice between two sequence points — you get undefined behavior. The compiler can do anything: from a correct result to formatting your hard drive (joking, but almost true).

\n\n

Classic examples of sequence points

\n
    \n
  • The semicolon ; — end of an expression
  • \n
  • The comma , as an operator (but not in function arguments!)
  • \n
  • The end of each expression in a for loop (after initialization, condition, and increment)
  • \n
  • Logical operators && and ||
  • \n
  • The ternary operator ?:
  • \n
  • A function call (before the function body begins execution)
  • \n
\n\n

How do sequence points cause undefined behavior?

\n\n

Here is a classic example that every C++ developer should know:

\n\n
int i = 0;\ni = i++ + ++i;  // UB! Undefined behavior
\n\n

Why is this UB? Because between two sequence points (in this case, between the beginning and end of the expression), the variable i is modified twice:

\n
    \n
  1. The postfix increment i++
  2. \n
  3. The prefix increment ++i
  4. \n
  5. The assignment i = ...
  6. \n
\n\n

The compiler doesn't know in which order to perform these operations. The result? One compiler will output 2, another — 3, a third — 0. And in optimization mode, it might even discard this code entirely.

\n\n

Another tricky case: function arguments

\n\n
int foo(int a, int b) { return a + b; }\n\nint x = 5;\nint result = foo(++x, ++x);  // UB!
\n\n

Here, the comma between the arguments is not a comma operator, but a separator. There is no sequence point between the evaluation of the ++x arguments! Both increments happen "in parallel" (from the standard's perspective), and we again get undefined behavior.

\n\n

Practical examples: what UB looks like in real code

\n\n

Example 1: A non-obvious bug with indexing

\n\n
int arr[5] = {0, 10, 20, 30, 40};\nint idx = 1;\narr[idx] = idx++;  // UB!
\n\n

At first glance, it seems simple: we want to write 1 into arr[1], and then increment idx. But the standard does not guarantee the order of evaluating the index idx and the right-hand side idx++. The result? It might write to arr[1], or it might write to arr[2].

\n\n

Example 2: Comp

Blogs

Book Recommendations