C / LOOPS AND JUMPS
do-while and the loop that always runs once
Write do-while loops that test after the body, judge when a guaranteed first pass is correct, and avoid its scope and semicolon traps.
What you will learn
- Choose do-while only when the body must run before the test can mean anything
- Declare variables the condition tests outside the do; body scope ends at its brace
- Write the required semicolon after while (condition)
- Check the zero/empty case: the body still runs once when the guard starts false
Understanding do-while and the loop that always runs once
C spells the post-tested loop `do statement while ( expression ) ;` — one statement whose closing semicolon is part of the grammar, not decoration. The controlling expression is evaluated after each pass through the body, so the body always runs at least once and the condition answers "again?" rather than "may I?". A while loop puts a guard at the door; a do-while puts a turnstile at the exit.
That guaranteed first pass is sometimes the whole point. Peeling digits off an unsigned value with `n % 10` and `n /= 10` still has to emit something when n is already 0, and a read-then-validate cycle cannot judge input it has not read yet. The same property becomes a defect when the sequence may be empty: with a count of zero, `do { use(a[i++]); } while (i < n);` touches a[0] before the guard is ever consulted.
Two C-specific consequences follow from the body being a block. Identifiers declared inside it are out of scope in the controlling expression, so anything the condition inspects must be declared before the `do` — otherwise the code fails to compile, or quietly tests a same-named variable from the enclosing scope. And because `do { ... } while (0)` is a single statement that runs its body exactly once, it is the standard wrapper for multi-statement macros: the call site keeps its semicolon and still fits anywhere one statement is expected.
<stdio.h>
int main(void)
{
unsigned n;
n = 0;
printf("do-while, n = 0: [");
do {
putchar('0' + n % 10);
n /= 10;
} while (n != 0);
printf("]\n");
n = 0;
printf("while, n = 0: [");
while (n != 0) {
putchar('0' + n % 10);
n /= 10;
}
printf("]\n");
n = 250;
printf("do-while, n = 250: [");
do {
putchar('0' + n % 10);
n /= 10;
} while (n != 0);
printf("]\n");
return 0;
}
A do-while evaluates its condition after the body, so the body always runs once — use it only when that first pass is required, not merely harmless.
Worked examples
Read first, judge after
A validate-until-acceptable loop where the condition cannot be evaluated until the body has produced a value.
<stdio.h>
int main(void)
{
int typed[4] = { -4, 0, 12, 7 }; /* stand-ins for what a user types */
int i = 0;
int value; /* the condition needs it, so it lives out here */
do {
value = typed[i];
i++;
printf("read %d\n", value);
} while (value < 1 || value > 10);
printf("accepted %d after %d read(s)\n", value, i);
return 0;
}
Example explained
Line 1`value` is declared before the `do` because the controlling expression sits outside the body's scope.
Line 2The body obtains a value and the test judges it afterwards, which is exactly the shape input validation needs.
Line 3`i` advances inside the body, so each pass inspects a fresh element; the fourth one satisfies both bounds and the loop exits.
Line 4Had typed[0] already been 7, the body would still have run once — that pass is not wasted work, it is the read itself.
do { } while (0) as a macro wrapper
Shows why a multi-statement macro is wrapped in a loop that runs exactly once.
<stdio.h>
REPORT(tag, x)
int main(void)
{
int n = 3;
if (n > 5)
REPORT("big", n);
else
REPORT("small", n);
REPORT("last", -1);
return 0;
}
Example explained
Line 1The two printf calls are wrapped in `do { ... } while (0)`, so the expansion is one statement that still expects the semicolon written at the call site.
Line 2That is what lets `REPORT("big", n);` serve as the unbraced then-branch while the `else` on the next line still parses.
Line 3With bare braces the call site would expand to `{ ... };`, and the stray empty statement after the block turns the `else` into a syntax error.
Line 4`while (0)` is false the first time it is evaluated, so the body runs exactly once and the constant test folds away.
Important notes
The trailing semicolon belongs to the do statement itself; unlike `while (c) { }` or `for (;;) { }`, a `do { } while (c)` block is not a complete statement without it.
`continue` inside a do-while jumps to the controlling expression rather than the top of the body, and a `break` inside a `do { } while (0)` macro escapes the macro, not the caller's loop.
Common mistakes
Leaving off the semicolon after `while (condition)`: the do statement stays unfinished and the compiler reports an error on the following line, sending you to hunt in code you never touched.
Declaring the tested variable inside the body, as in `do { int c = next(); } while (c != 0);` — c's scope ends at the closing brace, so this either fails to compile or silently tests an outer variable that happens to share the name.
Reaching for do-while over a collection that can be empty: the first element is processed before `i < n` is ever checked, so a count of zero reads out of bounds and the behaviour is undefined.
Try it yourself
Change, predict, then run
Write a do-while that prints the digits of an unsigned value least-significant first, and run it for 1000 and for 0. Then change the same loop to a while loop and note which of the two inputs now prints nothing at all.
Open the C workspaceCheck your understanding
A helper is called with a count n that can legitimately be 0 and sums with `do { total += a[i]; i++; } while (i < n);`, where i starts at 0. What happens when n is 0?
- The body runs once, reads a[0] that the caller never provided, and the behaviour is undefined
- The body is skipped because i < n is false before the first pass
- The loop runs forever because the condition can never become true
- The compiler rejects the loop because n could make the condition false on entry
Show answer
A do-while evaluates its controlling expression only after a pass through the body, so `i < n` has no say about the first iteration and a[0] is read no matter what n is. Option 1 describes `while (i < n)`, which is the form this loop should have used: for an empty range the guard has to run before the body, not after it.