Operators: Assignment, Compound Assignment, Increment/Decrement
This chapter covers the assignment operator, the compound assignment operators, and the increment/decrement operators — including the pre/post distinction and the undefined behavior that arises when they are unsequenced.
Why This Matters
Assignment and increment/decrement are the workhorses of imperative C. Their value semantics and their sequencing rules are the source of some of the most frequently asked (and most frequently wrong) questions about C.
Prerequisites
c.core.8— expressions and evaluation.
Core Concept
Assignment
The assignment operator = stores a value in an object. Its left operand must be a modifiable lvalue. The value of the assignment expression is the value stored, after conversion to the type of the left operand.
x = 5; /* value of the expression is 5 */
y = x = 5; /* right-associative: both x and y become 5 */
Assignment is right-associative, so a = b = c is a = (b = c).
Compound assignment
Compound assignment combines an operation with assignment:
x += 5; /* x = x + 5 */
x -= 5; /* x = x - 5 */
x *= 2; /* x = x * 2 */
x /= 2; /* x = x / 2 */
x %= 3; /* x = x % 3 */
x <<= 1; /* x = x << 1 */
x >>= 1; /* x = x >> 1 */
x &= 0x0F; /* x = x & 0x0F */
x |= 0x0F; /* x = x | 0x0F */
x ^= 0x0F; /* x = x ^ 0x0F */
For x op= y, the left operand is evaluated once, whereas x = x op y evaluates x twice. This matters when the left operand has side effects (e.g., arr[i++] += 1).
Increment and decrement
x++; /* post-increment: value is old x, then x becomes old x + 1 */
++x; /* pre-increment: x becomes x + 1, value is new x */
x--; /* post-decrement */
--x; /* pre-decrement */
The operand must be a modifiable lvalue of scalar type.
Syntax
lvalue = expression;
lvalue op= expression;
++lvalue; --lvalue;
lvalue++; lvalue--;
Examples
Assignment value
#include <stdio.h>
int main(void)
{
int a, b, c;
a = b = c = 0; /* all become 0; assignment is right-associative */
printf("%d %d %d\n", a, b, c);
return 0;
}
Expected output: 0 0 0.
Compound assignment evaluates the left operand once
#include <stdio.h>
int main(void)
{
int arr[3] = {0, 0, 0};
int i = 0;
arr[i++] += 1; /* increments arr[0], then i becomes 1 */
printf("%d %d %d\n", arr[0], arr[1], i);
return 0;
}
Expected output: 1 0 1.
Pre vs. post
#include <stdio.h>
int main(void)
{
int x = 5;
int a = x++; /* a = 5, x = 6 */
int b = ++x; /* x = 7, b = 7 */
printf("%d %d %d\n", a, b, x);
return 0;
}
Expected output: 5 7 7.
How It Works
Assignment stores a value; the stored value (converted to the left type) is the result. Compound assignment performs the operation and the assignment as a single operation. Increment/decrement add or subtract 1.
The exact *timing* of the side effect relative to other evaluations is governed by sequencing rules (c.ops.sequencing).
Variations
Assignment conversions
The right operand is converted to the type of the left operand. This can truncate or change signedness (c.types.uac).
++/-- on pointers
Pointer increment/decrement advances by the size of the pointed-to type, not by one byte (c.ptr.arithmetic).
Common Mistakes
- Writing
if (x = 5)whenif (x == 5)was intended. This compiles and is a
classic bug.
- Assuming
a[i++] += 1anda[i] += 1; i++;are always identical — they are
for i, but the compound form evaluates i++ once.
- Writing
i = i++— this is undefined behavior (unsequenced modification and
read of i).
Undefined Behavior
- Unsequenced side effects on the same scalar object are UB. For example,
i = i++, i++ + i++, and a[i] = i++ are all UB. VERIFIED
- Incrementing a pointer that is not pointing into (or one-past-the-end of) a
valid array object is UB (c.ptr.arithmetic).
Portability
- The value semantics of assignment and
++/--are standard and portable. - The *result* of
++on a value that overflows (signed) is UB, as always for
signed overflow.
Under the Hood
x += 1 typically compiles to a single increment instruction; x = x + 1 may too, after optimization. The compiler tracks lvalues to know which memory (or register) to write.
Practical Usage
- Use compound assignment for clarity and to avoid double evaluation.
- Prefer pre-increment (
++i) in loops when the value is unused, as a
convention (modern compilers optimize both identically for scalars).
- Never write
i = i++.
Exercises
1. Demonstrate the value of an assignment expression by chaining assignments and printing the results. 2. Show the difference between a[i++] += 1 and a[i] += 1; i++; using an array and a loop. 3. Compile int i = 0; i = i++; with warnings and UBSan enabled; explain the diagnostic.
Deep Challenge
Explain, using the standard's sequencing rules, exactly why i = i++ is undefined behavior, and give a corrected version that produces a well-defined result for every possible input.
Related Concepts
c.core.13— precedence, associativity, sequencing.c.types.uac— conversions in assignment.c.ptr.arithmetic— pointer increment/decrement.
References
- ISO/IEC 9899:2018 §6.5.16 (assignment), §6.5.16.2 (compound assignment),
§6.5.2.4 (postfix increment/decrement), §6.5.3.1 (prefix increment/ decrement).
Verification
- Assignment is right-associative and yields the stored value.
VERIFIED - Compound assignment evaluates the left operand once.
VERIFIED i = i++is UB.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Assignment and its value
- [ ] Right-associativity
- [ ] Compound assignment operators
- [ ] Pre/post increment and decrement
- [ ] Unsequenced increment UB
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.ops.assign | 0 | 5 |
| c.ops.incdec | 0 | 5 |