Operators, expressions and input · about 40 min

Assignment, increment and side effects

Why this matters

Changing variables is what imperative programs do, and C gives you a compact toolkit for it: assignment that is itself an expression, compound operators like +=, and the famous ++. You will see count++, total += x and i = j = 0 in almost every C program. The same compactness lets you write lines whose meaning is undefined, and the C language really does refuse to say what i = i++ does. This lesson teaches the idioms and the one rule that keeps you out of trouble.

Assignment is an expression

In C, x = 5 is not just a statement. It is an expression whose value is the value assigned, and storing into x is a side effect of evaluating it. Adding a semicolon makes it an expression statement, which evaluates the expression and throws away the value.

Because it is an expression, it can appear inside other expressions:

int n = 10;
printf("%d\n", n = 20);   /* stores 20 into n, prints 20 */

int x, y, z;
x = y = z = 0;            /* = groups right to left: x = (y = (z = 0)) */
20

Chained assignment to initialize several variables is a common idiom. Assignment inside conditions is also common in real code (while ((c = getchar()) != EOF), which you will meet in the next module), but it needs care, because of the = versus == trap from the last lesson.

The left side of = must be something that designates a storage location, called an lvalue: a variable, an array element, a struct member, a dereferenced pointer. 5 = x and a + b = 3 are errors (lvalue required as left operand of assignment).

Compound assignment

x op= y means x = x op y, with x evaluated only once:

compoundmeans
x += yx = x + y
x -= yx = x - y
x *= yx = x * y
x /= yx = x / y
x %= yx = x % y

There are also &=, |=, ^=, <<= and >>= for the bitwise operators of module 13.

int score = 10;
score += 5;    /* 15 */
score *= 2;    /* 30 */
score -= 3;    /* 27 */
score /= 4;    /* 6, integer division */
score %= 4;    /* 2 */

Note that the right side is computed as a whole first: x *= a + b means x = x * (a + b), not x * a + b.

Increment and decrement

Adding or subtracting one is so common that C has dedicated operators, and they come in two forms:

expressioneffect on ivalue of the expression
++i (prefix)adds 1the new value
i++ (postfix)adds 1the old value
--isubtracts 1the new value
i--subtracts 1the old value
#include <stdio.h>

int main(void)
{
    int i = 5;
    int a = i++;   /* a gets 5, then i becomes 6 */
    int b = ++i;   /* i becomes 7, then b gets 7 */
    printf("a = %d, b = %d, i = %d\n", a, b, i);
    return 0;
}
a = 5, b = 7, i = 7

When the value is not used, as in a statement count++; on its own, the two forms do exactly the same thing and compile to the same instruction. C programmers mostly write i++ out of habit. The difference only matters when the expression's value is used.

Side effects and sequence points

A side effect is a change to the state of the program: storing into a variable (=, +=, ++), writing output, modifying a file. Evaluating x + 1 has no side effect; evaluating x++ does.

C deliberately does not fully specify when side effects happen within an expression. The compiler may reorder evaluation for speed. What C does guarantee is that all side effects are complete at certain sequence points, the most important being:

Between two sequence points, the rule is:

Do not modify a variable more than once, and do not both modify it and read it for any other purpose. Breaking this rule is undefined behavior.

int k = 1;
k = k++ + 1;              /* undefined: k modified by ++ and by = */

int m = 1;
printf("%d %d\n", m, m++);  /* undefined: m read and modified, unsequenced */

gcc warns about the simple cases:

main.c:6:7: warning: operation on 'k' may be undefined [-Wsequence-point]
    6 |     k = k++ + 1;
      |     ~~^~~~~~~~~
main.c:9:27: warning: operation on 'm' may be undefined [-Wsequence-point]

and on this compiler the printf happens to print 2 1, evaluating the arguments right to left. Another compiler, or another optimization level, may print 1 1. The order in which function arguments are evaluated is unspecified in C, and when one of them modifies a variable another one reads, the behavior is undefined.

The fix is always the same: split it into separate statements. x = x + 1; is fine (x is read only to compute its new value). m++; printf("%d\n", m); is fine.

The comma operator

The comma operator (different from the commas separating function arguments) evaluates its left operand, discards the value, then evaluates its right operand, with a sequence point between:

int a = (b = 3, b * 2);   /* b becomes 3, a becomes 6 */

Its main legitimate use is in for loops to update two variables at once: for (i = 0, j = n - 1; i < j; i++, j--). Elsewhere it mostly makes code harder to read.

Common mistakes

Recap

Going further: compile int i = 1; i = i++ + ++i; with -Wall and read the warning, then look up "Order of evaluation" on cppreference.com, which lists precisely what is sequenced and what is not.

Exercises

Exercise 1 · quiz

Prefix and postfix

What does this print?

int i = 5;
int a = i++;
int b = ++i;
printf("%d %d %d\n", a, b, i);
Answer
  • 5 7 7

i++ (postfix) increments i but its value is the old value: a gets 5, i becomes 6. ++i (prefix) increments first and its value is the new value: i becomes 7 and b gets 7.

Exercise 2 · quiz

Which lines are undefined?

Which of these statements have undefined behavior? Select all that apply. (i and j are int variables that hold small values.)

Answer
  • i = i++ + 1;

  • j = i++ + i++;

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

Modifying a variable twice, or modifying it and also reading it for another purpose, without a sequence point in between, is undefined. i = i++ + 1 modifies i through ++ and through =. i++ + i++ modifies i twice. In the printf, the arguments are unsequenced: one reads i while the other modifies it. i = i + 1 reads i only to compute the new value, which is fine, and the last line uses two statements, and the end of each statement is a sequence point.

Exercise 3 · write code

Keep score

A game score starts at 10 and goes through these steps:

  1. add 5
  2. multiply by 3
  3. subtract 7
  4. integer-divide by 4
  5. replace it by its remainder after dividing by 5

Implement each step on the variable score with a compound assignment operator (+=, -=, *=, /=, %=), printing the score after each step, exactly:

after add: 15
after multiply: 45
after subtract: 38
after divide: 9
after remainder: 4

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int score = 10;
    score = score + 5;
    printf("after add: %d\n", score);
    /* the remaining four steps, each with a compound operator */
    return 0;
}

Example

expected output

after add: 15
after multiply: 45
after subtract: 38
after divide: 9
after remainder: 4
Hint 1

score += 5; means score = score + 5;.

Hint 2

The others are *=, -=, /= and %=.

Reference solution
#include <stdio.h>

int main(void)
{
    int score = 10;
    score += 5;
    printf("after add: %d\n", score);
    score *= 3;
    printf("after multiply: %d\n", score);
    score -= 7;
    printf("after subtract: %d\n", score);
    score /= 4;
    printf("after divide: %d\n", score);
    score %= 5;
    printf("after remainder: %d\n", score);
    return 0;
}

Exercise 4 · write code

Swap without a temporary

Two lessons ago you swapped variables with a temporary. With arithmetic it can be done without one. Starting with a = 3 and b = 7, swap them using only the two existing variables and compound assignments or plain assignments (three statements are enough), so the existing printf prints exactly:

a = 7, b = 3

Do not declare any other variable, and do not change the printf.

(This is a puzzle, not a recommended technique: with a temporary the code is clearer, and the arithmetic version can overflow for large values.)

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int a = 3;
    int b = 7;

    /* swap using only a and b */

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

Example

expected output

a = 7, b = 3
Hint 1

Store the sum in a: after a += b;, a holds both values combined.

Hint 2

From the sum you can recover either original value by subtracting the other.

Hint 3

a += b; b = a - b; a -= b;

Reference solution
#include <stdio.h>

int main(void)
{
    int a = 3;
    int b = 7;

    a += b;      /* a = 10 */
    b = a - b;   /* b = 3  */
    a -= b;      /* a = 7  */

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