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
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++modifiesi++imodifiesi- 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 ofi- assignment writes to
iagain - 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:
++wfirst, thenwwfirst, 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++modifiesyx[y]readsyto 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.
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:
oldgets the original value ofi, which is3- then
ibecomes4 - no other unsequenced use of
iappears 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:
-
int i = 2;istarts as2
-
int a = i++;i++returns the old value:2abecomes2- then
iis incremented to3
-
int b = ++i;++iincrements firstibecomes4- the expression value is
4 bbecomes4
-
printf(...)- prints
i=4 a=2 b=4
- prints
Now compare that to this invalid example:
int i = 2;
int a = i++ + ++i; // undefined behavior
Why invalid?
i++modifiesi++ialso modifiesi- 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.
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:
volatileaffects 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
| 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 |
|---|---|---|
++i | increment first | new value |
i++ | use old value, then increment | old 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
| Style | Example | Recommendation |
|---|---|---|
| Compact but risky | x[y] = y++; | Avoid |
| Explicit and safe | x[y] = y; y++; | Prefer |
Assignment vs increment side effect
| Code | Status | Why |
|---|---|---|
i++; | Valid | one clear modification |
i = i + 1; | Valid | explicit read then assignment |
i = i++; | Undefined | multiple writes to i in one expression |
i = ++i; | Undefined | modification and assignment conflict |
i = i++ + ++i; | Undefined | multiple unsequenced modifications |
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.
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.
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
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
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.