The Statement That Repeats

The previous lesson gave you the statement that chooses. This one gives you the statement that repeats, and with it the last piece of ordinary control flow. Every program so far has run each line at most once; a loop runs a block again and again for as long as a condition holds. while is the plain form, and it behaves like an if that goes back for another look: it evaluates the condition, runs the body if the value is nonzero, then returns to the condition. Before is the operative word, because a while whose condition is already false never runs its body at all. A counted loop needs three things arranged around that condition, and they are always the same three: somewhere to start, the condition that decides whether to keep going, and an update that moves toward stopping. for gathers all three onto one line separated by semicolons, so the while below could be written for (int i = 0; i < 3; ++i) with the same braced body. That form also lets the counter be declared in the initializer, which is what this course does everywhere: the counter then exists only inside the loop, which is the entire reason to write it there. The ++i is the prefix increment lesson 2 recommended as the statement form, and a loop header is where you will write it most.

#include <stdio.h>

int main(void)
{
    int i = 0;

    while (i < 3)
    {
        printf("while pass %d\n", i);
        ++i;
    }

    printf("the loop ended with i at %d\n", i);

    return 0;
}
while pass 0
while pass 1
while pass 2
the loop ended with i at 3

The while scatters its three parts across three places, with the start above the loop and the update buried at the bottom of the body, while the for would put them side by side where a reader checks them in one glance; scope follows the same split, since this i outlives the loop and still holds 3 afterwards, while a counter declared in a for header ceases to exist the moment its loop ends. Now delete the ++i from the body and read the condition again. Nothing changes i, so i < 3 is true forever and the program prints while pass 0 until something kills it. A loop whose body never touches the value its condition tests is an infinite loop, and it is the cheapest loop bug to find: read the condition, then go looking for the line that moves what it tests. Brace every loop body, for the reason the previous lesson gave for if.

Now change that condition to i <= 3 and run it again. A fourth line appears, while pass 3, and the last line reads the loop ended with i at 4. One character, one extra pass, and this is the fencepost problem: a fence 3 metres long with a post every metre needs 4 posts, and the numbers from 0 to 3 inclusive are 4 numbers, because 0 is one of them. Starting at 0 and stopping at < n gives exactly n passes with the counter taking every value from 0 up to n - 1, so the bound you typed is the count; starting at 1, or switching to <=, changes the count without changing how the line looks. That is why count from 0 and use < is the default idiom rather than merely one option. One other limit belongs here: a counter that would have to climb past INT_MAX is signed overflow, and lesson 1 established that signed overflow is undefined behaviour rather than a wrap, so the condition has to stop the counter before the type does.

The Countdown That Cannot End

The next program is wrong, and its job is to count down from 2 to 0.

#include <stdio.h>

int main(void)
{
    unsigned int total = 3;

    for (unsigned int i = total - 1; i >= 0; --i)
    {
        printf("%u\n", i);
    }

    return 0;
}
countdown.c: In function 'main':
countdown.c:7:40: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]
    7 |     for (unsigned int i = total - 1; i >= 0; --i)
      |                                        ^~

i is unsigned, and an unsigned type has no negative half, so i >= 0 is not a question at all. It is the constant 1. The loop prints 2, 1 and 0 as intended, and then --i on a zero unsigned value wraps to 4294967295 exactly as lesson 1 said it must, and the countdown starts over from a number nobody asked for. Nothing here is undefined behaviour, which is what makes it so persistent: unsigned wrap is defined, the condition is defined, and the result is a perfectly well-formed infinite loop. gcc sees through it, but only with -Wextra, which is where -Wtype-limits lives. The fix is to count down in a signed counter, so int total = 3; with for (int i = total - 1; i >= 0; --i) prints 2, 1, 0 and stops, because -1 is a value an int can hold and the condition can finally turn false. The rule generalises past countdowns: an unsigned value compared >= 0 is always true and one compared < 0 is always false, so a condition written either way is a condition doing no work.

Leaving Early and Skipping Ahead

Two statements interrupt a loop from inside its body. break ends the loop immediately and control resumes after it, the same break that ends a switch case. continue abandons only the current pass and goes on to the next, which in a for loop means the update runs first.

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 8; ++i)
    {
        if (i % 2 != 0)
        {
            continue;
        }
        if (i > 6)
        {
            break;
        }
        printf("even and not past 6: %d\n", i);
    }

    return 0;
}

That header starts at 1 and uses <= because the loop is over the numbers 1 to 8 themselves rather than over a count of passes, which is the case the previous section's default does not cover. The counter visits 1 through 8 and three lines come out, even and not past 6: 2, then 4, then 6. Odd values hit the continue and never reach the printf, and when i reaches 8 the second guard hits break, so 8 never prints and the loop is finished rather than merely skipping a pass. That difference is the whole of it: continue goes round again, break gets out. Notice where the continue sits, at the top, as a guard. The same filter written as if (i % 2 == 0) { ... } wrapped around everything below would indent the real work one level deeper to express a condition that is only a doorman, and each further filter would add another level, while a guard states the reason to skip and leaves the body flat. One trap comes with the pair: a continue in a for loop still runs the update in the loop header, while a continue in a while loop runs nothing you did not write, so jumping over a while body's own ++i is a direct route back to the infinite loop above.

The Loop That Acts Before It Asks

do ... while moves the condition to the bottom, so the body always runs at least once and the condition decides only whether to run it again.

#include <stdio.h>

int main(void)
{
    int value = 0;
    int remaining = value;
    int digits = 0;

    do
    {
        ++digits;
        remaining /= 10;
    } while (remaining != 0);

    printf("%d has a digit count of %d\n", value, digits);

    return 0;
}

That prints 0 has a digit count of 1, and the zero is the reason this is a do loop. Dividing by 10 until nothing is left counts the digits of a number, and setting value to 4096 gives 4096 has a digit count of 4, but 0 is already nothing left, so a while (remaining != 0) testing first would report that 0 has no digits at all. Anything that must happen once before it can be judged wants this shape. Two details of the syntax. The condition is written after the closing brace and ends in a semicolon, because a do loop is one statement and that is its terminator; leaving it out is a syntax error rather than a quiet bug, which is the one kindness in it. And remaining exists because the loop destroys what it counts, so the original is kept for the report, a habit worth having whenever a loop consumes its input. Reach for do only when the run-at-least-once is genuinely part of the problem. A plain while is the right default, and a do loop whose condition is already known on entry is just a while with its test hidden at the bottom.

Reading Until scanf Stops Converting

Chapter 1 finished by printing the number scanf returns without being able to act on it, and the previous lesson turned that number into a guard for a single read. This is what it was building to, because the same number is what tells a loop the input has run out.

#include <stdio.h>

int main(void)
{
    int value = 0;
    int count = 0;
    long total = 0;

    while (scanf("%d", &value) == 1)
    {
        ++count;
        total += value;
    }

    if (count == 0)
    {
        printf("no numbers read\n");
        return 0;
    }

    printf("read %d numbers, total %ld, average %ld\n", count, total, total / count);

    return 0;
}

Given 12 5 30 stop it prints read 3 numbers, total 47, average 15. The condition is the read, which is the shape to take away from this lesson. scanf("%d", &value) returns the number of conversions it completed, so == 1 means it converted the one value it was asked for and the body may safely use it, while every other answer ends the loop: 0 when the next characters are not a number, as stop is here, and the negative value EOF when the input has run out entirely. One comparison handles both endings, so the loop needs no sentinel value and no count announced in advance, and 4 8 15 16 23 42 with nothing after it prints read 6 numbers, total 108, average 18 by ending on EOF instead. Empty input and hello both print no numbers read, and that branch exists because total / count with a zero count is the division by zero the previous lesson refused to allow; empty input is a real run, not a hypothetical. The accumulators are declared and zeroed before the loop because they must outlive it, the mirror image of the counter belonging inside a for header, and total is a long because a stream of unknown length is precisely where int addition would overflow. The average truncates toward zero because integer division always does, so 47 over 3 reports 15.

Key Takeaways

  • while tests before each pass and may run its body zero times, for gathers the start, condition and update onto one line, and do ... while tests after the body and so always runs it at least once, with a semicolon after the closing while (...). Brace every loop body.
  • Declare the counter in the for initializer, as in for (int i = 0; i < n; ++i), so it exists only inside the loop, and use the prefix ++i as the statement form.
  • A loop whose body never changes what its condition tests never ends. Count from 0 and use <, because i < n runs exactly n times while i <= n runs n + 1; that fencepost difference is the whole off-by-one family. A counter climbing past INT_MAX is signed overflow, which is undefined behaviour.
  • An unsigned counter is never negative, so for (unsigned int i = n - 1; i >= 0; --i) loops forever: the condition is always true, and --i at 0 wraps to a huge value. gcc reports comparison of unsigned expression in '>= 0' is always true [-Wtype-limits] under -Wextra. Count down in a signed counter.
  • break ends the loop and continue starts the next pass. A continue guard at the top of the body reads better than wrapping the body in an if, but remember that continue runs a for header's update and skips a while body's.
  • while (scanf("%d", &value) == 1) reads until the input stops converting, ending on non-numeric text and on EOF alike, with no sentinel needed. Accumulate inside the loop, report after it, and guard the empty case before dividing by the count.