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
| situation | loop |
|---|---|
| known number of passes, a counter | for |
| repeat until a condition changes, maybe zero times | while |
| repeat, but the body must run at least once | do-while |
| repeat forever, leave from inside | for (;;) 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
while (cond)tests first and may run zero times;do { } while (cond);runs at least once.for (init; cond; update)packages a counting loop; a variable declared ininitis local to the loop.- Loops are conditional jumps backward; something in the body must make the condition false.
for (int i = 0; i < n; i++)runs exactly n times and is the C idiom.- Watch for off-by-one, stray semicolons, missing braces, unsigned countdowns and float counters;
-Wall -Wextracatches several of these.
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.