Question

Undefined Behavior with Pre-Increment and Post-Increment in C

cincrementundefined-behavioroperator-precedencesequence-points

Question

I am trying to understand why several C expressions that use pre-increment (++i) and post-increment (i++) are considered undefined behavior.

Here is the code:

#include <stdio.h>

int main(void)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i);

    i = 1;
    i = (i++);
    printf("%d\n", i);

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u);

    u = 1;
    u = (u++);
    printf("%d\n", u);

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v);

    int w = 0;
    printf("%d %d\n", ++w, w);

    int x[2] = { 5, 8 }, y = 0;
    x[y] = y++;
    printf("%d %d\n", x[0], x[1]);
}

I expected some of these statements to produce predictable results, such as 1 1 for printf("%d %d\n", ++w, w) or 1 for i = (i++). Why are these expressions undefined in C, and how should I reason about them correctly?

Short Answer

By the end of this page, you will understand why modifying and reading the same variable multiple times inside one C expression can cause undefined behavior. You will learn how pre-increment and post-increment work, why evaluation order matters, what sequence points and sequencing rules mean in practice, and how to rewrite unsafe expressions into clear, correct code.

Concept

In C, ++i and i++ do more than just "add 1". They both modify a variable, and that matters because C does not guarantee the order in which many parts of an expression are evaluated.

The core rule is:

  • If a scalar object is modified more than once in a single expression without proper sequencing, the behavior is undefined.
  • If a scalar object is modified and also read for another purpose in the same expression without proper sequencing, the behavior is also undefined.

A scalar object includes things like int, char, pointers, and similar simple values.

Why this matters

When C says behavior is undefined, it does not mean "hard to predict" or "compiler-dependent but limited." It means:

  • the program has no valid meaning according to the language rules
  • the compiler can produce any result
  • different compilers or optimization levels may behave differently
  • even the same compiler may produce different results in different builds

Pre-increment vs post-increment

  • ++i
    • increments i
    • the expression value is the new value
  • i++
    • the expression value is the old value
    • the increment happens as part of the expression evaluation, but not in a way that lets you safely combine it with unrelated reads/writes of the same variable

These operators are safe when used alone:

i++;
++i;

They become dangerous when mixed with other uses of the same variable in one expression:

i = i++ + ++i;   // undefined behavior
printf("%d %d\n", ++i, i); // undefined behavior
x[i] = i++;      // undefined behavior

The key idea: sequencing

In older C explanations, this is often described using sequence points. In modern wording, people often talk about operations being sequenced before, sequenced after, or unsequenced.

For beginners, the practical rule is simple:

If one expression both changes a variable and also uses that same variable again without a guaranteed ordering, do not write it.

Examples from the question

i = i++ + ++i;

This modifies i multiple times in one expression:

  • i++ modifies i
  • ++i modifies i
  • assignment also writes to i

There is no guaranteed safe ordering that makes this valid. Result: undefined behavior.

i = (i++);

This may look harmless, but it still has a problem:

  • i++ schedules a modification of i
  • assignment writes to i again
  • these actions are not properly sequenced for a valid meaning

So this is also undefined behavior.

printf("%d %d\n", ++w, w);

Function arguments are not guaranteed to be evaluated left to right in C.

So the compiler is free to evaluate:

  • ++w first, then w
  • w first, then ++w

Because one argument modifies w and the other reads w without guaranteed sequencing, this is undefined behavior.

x[y] = y++;

This also has an unsequenced read/write problem:

  • y++ modifies y
  • x[y] reads y to compute the array index

The language does not guarantee whether the index uses old y or incremented y, and the expression is therefore undefined behavior.

What volatile and register do not change

volatile does not make undefined behavior defined. It only tells the compiler that accesses to the object have special significance and should not be optimized away in the usual manner.

register also does not affect whether an expression is defined. It is only a storage-class hint.

So these remain undefined too:

volatile int u = 0;
u = u++ + ++u;

register int v = 0;
v = v++ + ++v;

The type qualifier or storage class does not change the sequencing rules.

Mental Model

Think of an expression as several workers trying to update the same whiteboard.

  • One worker reads the current value.
  • Another worker increments it.
  • Another worker writes a final result.

If you do this in separate statements, the workers line up and take turns.

i = 1;
i++;
printf("%d\n", i);

That is orderly.

But in an expression like this:

i = i++ + ++i;

multiple workers are trying to read and rewrite the same whiteboard at nearly the same time, and the language rules do not define who goes first. Since there is no guaranteed order, the whole expression has no reliable meaning.

A good mental rule is:

If your expression makes you ask "which happens first?", split it into multiple statements.

In C, compact code is not always safe code.

Take Quiz

Syntax and Examples

Basic increment syntax

int i = 5;

int a = ++i; // i becomes 6, a gets 6
int b = i++; // b gets 6, then i becomes 7

After this:

i == 7
a == 6
b == 6

Safe usage

Using increment as a standalone statement is clear and safe:

int i = 0;
i++;
++i;
printf("%d\n", i); // 2

Using it once in a larger expression can also be fine if the variable is not used elsewhere in that same expression:

int i = 0;
int x = i++;
printf("x=%d, i=%d\n", x, i); // x=0, i=1

Unsafe usage

int i = 0;
i = i++ + 1; // undefined behavior

Why? Because i is being modified by i++ and also written by the assignment in the same expression without proper sequencing.

Another unsafe example:

int i = 0;
printf("%d %d\n", i++, i); // undefined behavior

Safe rewrites

Instead of this:

i = (i++); // undefined behavior

write what you actually mean.

If you want i to increase by 1:

i++;

If you want to keep the old value somewhere:

int old = i;
i++;
i = old;

Instead of this:

x[y] = y++; // undefined behavior

write:

x[y] = y;
y++;

or:

int index = y;
x[index] = y;
y++;

A simple valid example

#include <stdio.h>

int main(void) {
    int i = 3;
    int old = i++;

    printf("old = %d\n", old);
    printf("i = %d\n", i);
    return 0;
}

Explanation:

  • old gets the original value of i, which is 3
  • then i becomes 4
  • no other unsequenced use of i appears in the same expression
  • this is well-defined

Step by Step Execution

Consider this valid example:

#include <stdio.h>

int main(void) {
    int i = 2;
    int a = i++;
    int b = ++i;

    printf("i=%d a=%d b=%d\n", i, a, b);
    return 0;
}

Step by step:

  1. int i = 2;

    • i starts as 2
  2. int a = i++;

    • i++ returns the old value: 2
    • a becomes 2
    • then i is incremented to 3
  3. int b = ++i;

    • ++i increments first
    • i becomes 4
    • the expression value is 4
    • b becomes 4
  4. printf(...)

    • prints i=4 a=2 b=4

Now compare that to this invalid example:

int i = 2;
int a = i++ + ++i; // undefined behavior

Why invalid?

  • i++ modifies i
  • ++i also modifies i
  • both occur in one expression
  • there is no guaranteed ordering that makes the expression valid in C

Because the program has undefined behavior, you should not trace it as if it had a fixed sequence. Any such trace would be invented, not guaranteed by the language.

Real World Use Cases

Increment operators are common in real C programs, especially in loops, parsers, and low-level code.

Common valid uses

Loop counters

for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}

Pointer traversal

char *p = buffer;
while (*p != '\0') {
    p++;
}

Reading arrays sequentially

int i = 0;
while (i < count) {
    total += values[i];
    i++;
}

Places where bugs happen

Parsing code

Trying to be too clever:

token = input[pos++] + input[++pos]; // dangerous and unclear

Better:

a = input[pos];
pos++;
b = input[pos];
pos++;
token = a + b;

Array updates

arr[index] = index++; // undefined behavior

This can silently create inconsistent results in different builds.

Logging and debugging

printf("%d %d\n", i++, i); // undefined behavior

Even debug code must follow the language rules.

Take Quiz

Real Codebase Usage

In real codebases, experienced C developers usually avoid writing expressions that modify and read the same variable in one statement unless the behavior is completely obvious and guaranteed.

Common patterns

Prefer simple statements

Instead of compressing logic into one line:

result = data[pos++] + offset[pos++];

developers often write:

int a = data[pos];
pos++;
int b = offset[pos];
pos++;
result = a + b;

This is easier to review and safer.

Use temporary variables for clarity

int index = y;
x[index] = y;
y++;

Temporary variables are common in production code because they make evaluation order explicit.

Avoid clever side effects in function calls

Instead of:

log_value(i++, i); // unsafe if order matters

write:

int old = i;
i++;
log_value(old, i);

Guard against maintenance bugs

A compact expression may look valid today but become unsafe after a future edit. Splitting statements reduces that risk.

Style guidance used by teams

Many C teams follow rules like:

  • one side effect per statement when practical
  • no modifying a variable more than once in an expression
  • do not depend on argument evaluation order
  • prefer readability over clever shorthand

These habits reduce undefined behavior bugs and make code easier to audit.

Common Mistakes

1. Assuming left-to-right evaluation

Many beginners expect C to evaluate expressions from left to right.

Broken example:

int w = 0;
printf("%d %d\n", ++w, w); // undefined behavior

Why it is wrong:

  • function argument evaluation order is not guaranteed here
  • one argument modifies w, the other reads it

How to avoid it:

++w;
printf("%d %d\n", w, w);

2. Thinking parentheses create sequencing

Broken example:

i = (i++); // undefined behavior

Why it is wrong:

  • parentheses only group expressions
  • they do not force a safe order for side effects

How to avoid it:

i++;

3. Believing volatile makes it safe

Broken example:

volatile int u = 1;
u = u++; // still undefined behavior

Why it is wrong:

  • volatile affects access rules and optimization assumptions
  • it does not repair unsequenced modifications or reads

4. Mixing indexing and incrementing the same variable

Broken example:

x[y] = y++; // undefined behavior

How to avoid it:

x[y] = y;
y++;

5. Trusting observed output

A program may print the same result many times and still be undefined.

int i = 1;
i = i++;
printf("%d\n", i);

Even if your compiler prints 1 or 2, that does not make the code correct.

6. Confusing unspecified and undefined behavior

  • Unspecified behavior: the language allows several possibilities
  • Undefined behavior: the language gives no valid meaning at all

Expressions like i = i++ + ++i; are in the undefined category, which is much worse.

Comparisons

Related concepts compared

ConceptMeaningExampleSafe to rely on?
Well-defined behaviorThe language specifies exactly what happensi = 5; i++;Yes
Unspecified behaviorOne of several allowed outcomes may happenfunction argument order in some casesNo, not for exact result
Undefined behaviorThe program has no valid meaning by the language rulesi = i++ + ++i;No

++i vs i++

ExpressionWhat happensExpression value
++iincrement firstnew value
i++use old value, then incrementold value

Both are fine when used carefully. Neither is safe if you also modify or depend on the same variable elsewhere in the same unsequenced expression.

Compact vs explicit style

StyleExampleRecommendation
Compact but riskyx[y] = y++;Avoid
Explicit and safex[y] = y; y++;Prefer

Assignment vs increment side effect

CodeStatusWhy
i++;Validone clear modification
i = i + 1;Validexplicit read then assignment
i = i++;Undefinedmultiple writes to i in one expression
i = ++i;Undefinedmodification and assignment conflict
i = i++ + ++i;Undefinedmultiple unsequenced modifications
Take Quiz

Cheat Sheet

Quick rules

  • ++i increments first, returns new value
  • i++ returns old value, then increments
  • Do not modify the same variable more than once in one expression
  • Do not read and modify the same variable in one expression unless sequencing is guaranteed
  • Parentheses group expressions; they do not make undefined code safe
  • volatile does not fix undefined behavior
  • register does not change expression semantics

Safe examples

i++;
++i;
int old = i++;
int new_value = ++i;

Unsafe examples

i = i++;
i = ++i;
i = i++ + ++i;
printf("%d %d\n", i++, i);
x[y] = y++;

Safe rewrites

/* instead of i = i++; */
i++;

/* instead of printf("%d %d\n", ++w, w); */
++w;
printf("%d %d\n", w, w);

/* instead of x[y] = y++; */
x[y] = y;
y++;

Best practice

If an expression both updates a variable and uses it again, split it into multiple statements.

FAQ

Why is i = i++ undefined in C?

Because i++ modifies i, and the assignment also writes to i in the same expression without proper sequencing.

Does volatile make u = u++ valid?

No. volatile does not change the sequencing rules or make undefined behavior defined.

Is printf("%d %d", ++w, w) always undefined?

Yes, in C this is undefined because one argument modifies w and another reads it without guaranteed evaluation order.

What is the difference between ++i and i++?

++i increments first and yields the new value. i++ yields the old value and then increments.

Are increment operators bad practice in C?

No. They are useful and common. The problem is using them multiple times on the same variable inside one expression.

How do I avoid undefined behavior with increments?

Use one side effect per statement when possible, and use temporary variables to make the order explicit.

Why does my compiler still print a consistent result?

Undefined behavior can appear to work on one compiler or build, but the language does not guarantee that result.

Is function argument order guaranteed in C?

Not in general. You should not write code that depends on a specific argument evaluation order.

Take Quiz

Mini Project

Description

Build a small C program that demonstrates the difference between safe increment usage and undefined behavior. The goal is to practice rewriting risky expressions into clear, well-defined statements.

Goal

Create a program that prints correct results using increment operators without relying on undefined behavior.

Requirements

  1. Create at least one example using ++i safely.
  2. Create at least one example using i++ safely.
  3. Rewrite one unsafe expression into multiple safe statements.
  4. Print the intermediate and final values so the execution is easy to follow.
Take Quiz

Keep learning

Related questions

View more