Question
Undefined Behavior with Pre-Increment and Post-Increment in C
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
- increments
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
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.
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 is being modified by and also written by the assignment in the same expression without proper sequencing.
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:
-
int i = 2;istarts as2
-
int a = i++;i++returns the old value:2abecomes2- then
iis incremented to3
-
int b = ++i;
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++;
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
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:
u = ;
u = u++;
Comparisons
Related concepts compared
| Concept | Meaning | Example | Safe to rely on? |
|---|---|---|---|
| Well-defined behavior | The language specifies exactly what happens | i = 5; i++; | Yes |
| Unspecified behavior | One of several allowed outcomes may happen | function argument order in some cases | No, not for exact result |
| Undefined behavior | The program has no valid meaning by the language rules | i = i++ + ++i; | No |
++i vs i++
| Expression | What happens | Expression value |
|---|
Cheat Sheet
Quick rules
++iincrements first, returns new valuei++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
volatiledoes not fix undefined behaviorregisterdoes 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.
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
- Create at least one example using
++isafely. - Create at least one example using
i++safely. - Rewrite one unsafe expression into multiple safe statements.
- Print the intermediate and final values so the execution is easy to follow.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.