C Part I · Module 3 · Expressions and Operators
Increment and Decrement
Adding one to an object is so common that C gives it dedicated operators, and they come in two forms that differ in a way beginners are usually taught backwards. Written on their own, as a complete statement, the two forms do exactly the same thing — and that is where the overwhelming majority of real uses are. The difference appears only when the expression's value is used for something, and it is a difference in what the expression hands back rather than in what happens to the object. This chapter establishes that clearly, and then addresses the family of clever one-line expressions built from these operators, where the honest answer is not a number but the observation that C has stopped promising one.
Foundation11 min readCPart IFundamentalsExpressions
Part I · Module 3 · Chapter 3.7 · Increment and Decrement
1. The Most Common Change of All
Chapter 3.4 gave you total += 1 for adding one to an object. It comes up so often — counting things, stepping through positions — that C provides something shorter still:
x++ add one to x
++x add one to x
x-- subtract one from x
--x subtract one from xTwo forms for each. The names are postfix (x++, the operator after the operand) and prefix (++x, before it).
2. On Their Own, the Forms Are Identical
Start where nearly all real use lives: the operator written as a complete statement, with the value ignored.
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 10;
a++;
++b;
printf("a %d, b %d\n", a, b);
a--;
--b;
printf("a %d, b %d\n", a, b);
return 0;
}Output:
a 11, b 11
a 10, b 10Identical. When the expression's value is discarded — which is what a statement like a++; does — there is no difference whatsoever between the two forms.
That is worth stating plainly because it removes an anxiety beginners carry unnecessarily. If you are writing count++; as a statement, you do not have to think about which form to use; choose one and be consistent. Many codebases prefer the prefix form out of habit inherited from other languages; in C, for a plain integer object, it makes no difference.
3. The Difference Is What the Expression Hands Back
The forms diverge only when the value is used. Both change the object the same way; they disagree about what the expression produces.
#include <stdio.h>
int main(void)
{
int x = 5;
int taken_before = x++;
int y = 5;
int taken_after = ++y;
printf("x++ : expression gave %d, x is now %d\n", taken_before, x);
printf("++y : expression gave %d, y is now %d\n", taken_after, y);
return 0;
}Output:
x++ : expression gave 5, x is now 6
++y : expression gave 6, y is now 6Both objects ended at 6. The expressions produced different values:
form object before expression produces object after
------ ------------- ------------------- ------------
x++ 5 5 6
++x 5 6 6So:
- Postfix
x++produces the value the object had before the increment. - Prefix
++xproduces the value the object has after it.
A memorable way to read it: the position of the ++ tells you when the change is reflected in the value you get. Written after the name, you get the old value and the change follows. Written before, the change comes first and you get the new value.
4. Where This Gets Dangerous
++ and -- have a side effect, which Chapter 3.1 defined as something an expression does besides producing a value. That makes them the first operators whose entire purpose is the side effect — and it makes them easy to misuse.
The tempting move is to pack several into one expression:
i = i++ + 1; /* do not write this */
x = x++ - --x; /* nor this */These look like puzzles with answers. They are not puzzles, and here is the honest statement:
An expression that modifies the same object more than once, or modifies an object and separately reads it, has undefined behaviour in C. The standard imposes no requirements, so there is no answer to compute.
This chapter will not show you what those lines print. Running one would produce some output on one compiler with one set of options, and the only lesson available from it is the harmful one: that the number means something. Chapter 2.6 made the same refusal about signed overflow for the same reason.
Two practical consequences:
The rule you need now is simple. In any one expression, modify a given object at most once, and do not also read it separately for an unrelated purpose. a = b++ + c; is fine — b is modified once and a and c are untouched. i = i++ + 1; is not.
Compilers may or may not warn. Some diagnose the obvious cases; none can catch all of them. This is your discipline, not a check you can delegate.
5. Using Them Well
The good uses are unglamorous and that is the point.
#include <stdio.h>
int main(void)
{
int retries = 0;
retries++;
retries++;
retries++;
printf("retries: %d\n", retries);
return 0;
}Output:
retries: 3Each statement changes one object and discards the value. Nothing is ambiguous and nothing depends on ordering.
When you do want the value, use it once and keep the expression simple. int previous = counter++; is clear: take the current value, then step the counter. Nobody has to reconstruct anything.
The place you will meet ++ most often is a construct this module has not reached yet — the counting loop in Module 4 — where i++ appears in a position designed for exactly this and is entirely unambiguous. That is worth knowing now so the operator does not look exotic when it turns up there.
6. What You Should Be Able to Say Now
++adds one to an object;--subtracts one. Each has a postfix and a prefix form.- Written as a statement on their own, the two forms are identical. That covers most real use.
- They differ only in what the expression produces.
- Postfix produces the value from before the change; prefix produces the value after it.
- The object ends up the same either way, and neither form is faster or safer for a plain integer.
- These operators exist for their side effect, which is what makes them easy to misuse.
- Modifying the same object more than once in one expression is undefined behaviour — there is no answer, not merely a surprising one.
- Modify an object at most once per expression, and do not separately read it for another purpose.
- Compilers catch some of these and cannot catch all of them.
7. Exercise
Exercise 1 — fill in the trace. For int n = 3; complete the table for each form:
expression n before expression produces n after
n++ 3 ? ?
++n 3 ? ?
n-- 3 ? ?
--n 3 ? ?Exercise 2 — predict, then run. Give the exact output:
int p = 7;
int q = p++;
int r = ++p;
printf("p %d, q %d, r %d\n", p, q, r);Exercise 3 — say when it matters. For each, say whether replacing ++i with i++ would change anything, and why:
(a) i++; as a complete statement
(b) int taken = i++;
(c) total += i++;
Exercise 4 — classify, do not evaluate. For each, say whether it is well defined or undefined, and give the reason. Do not try to work out a value for the undefined ones:
(a) a = b++ + c;
(b) i = i++ + 1;
(c) x = y++ + y++;
(d) count++;
Exercise 5 — repair it. Someone writes i = i++ + 1; intending to add one to i. Give two separate correct ways to write what they meant, and say which you would prefer.
Exercise 6 — explain the refusal. This chapter declined to show what i = i++ + 1; prints. Explain in two sentences why showing it would be worse than useful.
8. Summary
++and--change an object by one, in a postfix and a prefix form.- As standalone statements the two forms are identical, and that is where most use lives.
- They differ in the value the expression hands back: postfix gives the old value, prefix the new one.
- The object ends up the same either way.
- They exist for their side effect, which is what makes them prone to misuse.
- Modifying one object twice in a single expression is undefined — there is no answer to learn, and no output worth looking at.
- One modification per object per expression keeps you entirely clear of the problem.
- Do not rely on the compiler to catch it.
This chapter set a rule without giving its reason: modify an object once per expression, because otherwise C promises nothing. The reason involves how C decides when things happen inside an expression, and it turns out to be far looser than the left-to-right reading everyone assumes. Two chapters from now that becomes the subject. First, though, this module still owes you the operators that work on the bit patterns Module 2 spent so long establishing.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
