Control flow · about 50 min

Loops: while, do-while and for

Why this matters

Computers are useful because they repeat work tirelessly: process every line of a file, retry a connection, simulate a million time steps, sum a column of numbers. Every one of those is a loop. C has three loop statements. They are all built from the same conditional jump you saw with if, just pointing backward, and choosing the right one makes intent obvious to readers.

while: repeat as long as a condition holds

while (condition)
    statement

The condition is tested before each pass. If it is nonzero, the body runs and the test is repeated; once it is zero, execution continues after the loop. If the condition is false the first time, the body never runs.

int n = 3;
while (n > 0) {
    printf("%d...\n", n);
    n--;
}
printf("liftoff\n");
3...
2...
1...
liftoff

Trace it: n is 3, 3 > 0, print, n becomes 2; 2 > 0, print, n becomes 1; 1 > 0, print, n becomes 0; 0 > 0 is false, leave. Something in the body must move the condition toward false, or the loop never ends.

Underneath, a loop is a test plus a jump backward:

TOP:
    compare n with 0
    jump-if-less-or-equal  to DONE   <- condition false: leave
    call printf(...)
    n = n - 1
    jump TOP                         <- go back and test again
DONE:
    call printf("liftoff")

Use while when you do not know in advance how many passes are needed: "until the user types quit", "until the number reaches 1", "until end of input".

do-while: run the body at least once

do
    statement
while (condition);

The test comes after the body, so the body always runs at least once. Note the semicolon after the closing parenthesis.

int n = 0;
int digits = 0;
do {
    n = n / 10;
    digits++;
} while (n != 0);
printf("%d\n", digits);   /* 1: zero has one digit */

With a plain while, the input 0 would skip the body and report 0 digits. do-while is the right tool when the first pass must happen regardless: counting digits, showing a menu before asking for a choice, or reading input until it is valid. It is the least common of the three loops, but when it fits, it removes a special case.

for: counting loops

Most loops have the same three parts: set up a variable, test it, update it. for puts all three on one line:

for (initialization; condition; update)
    statement
int total = 0;
for (int i = 1; i <= 100; i++)
    total += i;
printf("1 + ... + 100 = %d\n", total);
1 + ... + 100 = 5050

The order of events:

  initialization            int i = 1          (once)
        |
        v
  +-> condition? ---- false ----> leave the loop
  |     | true
  |     v
  |   body                  total += i
  |     |
  |     v
  +-- update                i++

A for loop is exactly equivalent to this while loop (apart from a detail about continue covered in the next lesson):

{
    int i = 1;
    while (i <= 100) {
        total += i;
        i++;
    }
}

A variable declared in the initialization (C99 and later) exists only inside the loop; using i after the loop gives error: 'i' undeclared. That is a feature: the counter cannot leak into the rest of the function.

Any of the three parts may be left out. for (;;) with no condition loops forever, and is the idiomatic way to write "loop until something inside breaks out".

The idiomatic counting loop

To do something n times, C programmers write:

for (int i = 0; i < n; i++)

Start at 0, test with <, not <=. This form will feel natural once you meet arrays, whose indexes run from 0 to n - 1, and it makes the number of passes obvious: exactly n.

Choosing a loop

situationloop
known number of passes, a counterfor
repeat until a condition changes, maybe zero timeswhile
repeat, but the body must run at least oncedo-while
repeat forever, leave from insidefor (;;) or while (1)

All three can express any loop; pick the one that makes the reader's job easiest.

Common mistakes

Off-by-one errors. for (int i = 1; i < 10; i++) runs 9 times, not 10. Ask two questions of every loop: what is the first value, and what is the last value for which the body runs?

A semicolon after the loop header. The semicolon becomes the (empty) body:

for (int i = 0; i < 5; i++);      /* loop does nothing, 5 times */
    sum += 1;                     /* runs once, after the loop */
warning: this 'for' clause does not guard... [-Wmisleading-indentation]
note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'

Forgetting braces around a multi-line body. Only the first statement is repeated. If the missing statement was the update, the loop never ends:

int i = 0;
while (i < 3)
    printf("%d\n", i);
    i++;            /* not in the loop: prints 0 forever */

A program stuck like this keeps running until you press Ctrl+C (in the exercise checker, it is stopped after 10 seconds with "timed out").

Unsigned countdown. for (unsigned i = 3; i >= 0; i--) never ends, because an unsigned value is always >= 0 and decrementing 0 wraps around to 4294967295:

warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]

Floating-point loop counters. 0.1 cannot be stored exactly, so adding it ten times does not give exactly 1.0:

for (double x = 0.0; x != 1.0; x += 0.1)   /* never equals 1.0: runs forever */

Printed with 17 digits, the tenth value is 0.99999999999999989, and the next is 1.09999999999999987. Count with an integer and compute the double from it: for (int i = 0; i <= 10; i++) { double x = i / 10.0; ... }.

Recap

Going further: rewrite the times-table exercise with a while loop and then with a do-while loop, and compare which reads best. Try gcc -S -O0 on a small for loop and find the backward jump.

Exercises

Exercise 1 · quiz

How many times?

How many times does the body of this loop run?

for (int i = 1; i < 10; i += 3)
    printf("%d\n", i);
Answer
  • 3

i takes the values 1, 4 and 7. After the third pass i becomes 10, the test 10 < 10 is false, and the loop ends. A reliable way to answer these is to write down the loop variable at each test, including the final test that fails.

Exercise 2 · write code

A times table

Read an integer n and print its multiplication table from 1 to 10, one line each, in exactly this format (shown for n = 7):

7 x 1 = 7
7 x 2 = 14
...
7 x 10 = 70

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    printf("%d x %d = %d\n", n, 1, n * 1);
    return 0;
}

Example

input

7

expected output

7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70

Example

input

-2

expected output

-2 x 1 = -2
-2 x 2 = -4
-2 x 3 = -6
-2 x 4 = -8
-2 x 5 = -10
-2 x 6 = -12
-2 x 7 = -14
-2 x 8 = -16
-2 x 9 = -18
-2 x 10 = -20
Hint 1

A for loop with int i = 1; i <= 10; i++ visits 1 through 10.

Hint 2

Move the existing printf into the loop body and replace the 1s with i.

Reference solution
#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    for (int i = 1; i <= 10; i++)
        printf("%d x %d = %d\n", n, i, n * i);
    return 0;
}

Exercise 3 · write code

Collatz steps

The Collatz process starts from a positive integer n and repeats: if n is even, halve it; if n is odd, replace it with 3n + 1. It is conjectured (but not proven) that this always reaches 1.

Read a positive integer and print how many steps it takes to reach 1, as steps: K. For example 6 goes 6, 3, 10, 5, 16, 8, 4, 2, 1, which is 8 steps, so the output is steps: 8. For 1 the answer is steps: 0.

If the input is not a positive integer, print need a positive integer and exit with status 1. Use a while loop: you do not know in advance how many steps there will be. (long is used for n because 3n + 1 can grow past the range of int for some inputs; print it with %ld if you need to.)

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    long n;
    if (scanf("%ld", &n) != 1 || n < 1) {
        printf("need a positive integer\n");
        return 1;
    }
    int steps = 0;
    /* loop until n is 1 */
    printf("steps: %d\n", steps);
    return 0;
}

Example

input

6

expected output

steps: 8

Example

input

1

expected output

steps: 0
Hint 1

The loop condition is n != 1.

Hint 2

Inside the loop, an if/else updates n, then steps++ counts the step.

Reference solution
#include <stdio.h>

int main(void)
{
    long n;
    if (scanf("%ld", &n) != 1 || n < 1) {
        printf("need a positive integer\n");
        return 1;
    }
    int steps = 0;
    while (n != 1) {
        if (n % 2 == 0)
            n = n / 2;
        else
            n = 3 * n + 1;
        steps++;
    }
    printf("steps: %d\n", steps);
    return 0;
}

Exercise 4 · write code

Count the digits

Read an integer (it may be negative or zero) and print how many decimal digits it has, as just the number. Examples: 4096 has 4 digits, -75 has 2, and 0 has 1.

Count by repeatedly dividing by 10. Think about which loop form gives the right answer for 0 without a special case.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    int count = 0;
    while (n != 0) {
        n = n / 10;
        count++;
    }
    printf("%d\n", count);
    return 0;
}

Example

input

4096

expected output

4

Example

input

-75

expected output

2
Hint 1

Run the starter with input 0: the while loop body never executes.

Hint 2

A do-while loop always runs its body at least once.

Hint 3

Negative numbers need no special handling: in C, -75 / 10 is -7 (division truncates toward zero), and eventually you reach 0.

Reference solution
#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    int count = 0;
    do {
        n = n / 10;
        count++;
    } while (n != 0);
    printf("%d\n", count);
    return 0;
}

Exercise 5 · quiz

The loop that never ends

What happens when this runs?

for (unsigned int i = 3; i >= 0; i--)
    printf("%u\n", i);
Answer
  • It never stops: after 0, i wraps around to 4294967295 and keeps going

An unsigned value is never negative, so i >= 0 is always true. Decrementing an unsigned 0 is well defined: it wraps to the largest value, UINT_MAX (4294967295 for 32-bit unsigned). gcc -Wextra warns "comparison of unsigned expression in '>= 0' is always true". To count down to zero with an unsigned counter, write for (unsigned i = 4; i-- > 0; ) or just use a signed int.