C / LOOPS AND JUMPS
for loops and the anatomy of the header
Read and write any for header knowing its three clauses run on different schedules: once, before every test, and after every finished body.
What you will learn
- Name which for clause runs once, which runs before each test, and which after the body
- Leave any clause of a for header empty without dropping either semicolon
- Declare the counter in the header so its scope ends with the loop
- Drive two indices in one header with the comma operator
Understanding for loops and the anatomy of the header
A for header is written for (clause-1; expression-2; expression-3), and the two semicolons are structure, not separators you may swap for commas. Clause 1 is evaluated exactly once, before anything else happens; expression 2 is evaluated before every attempt to run the body; expression 3 is evaluated after every body that finishes. The keyword itself counts nothing. It only fixes that schedule, and whatever counting occurs is whatever you wrote into the slots.
Only the middle clause is a condition: it must be a scalar expression and it is compared against zero. The other two are evaluated for their side effects and their values are thrown away, which is why an assignment, a function call, or nothing at all is equally legal there. Any of the three clauses may be empty, and an empty condition is defined to be a nonzero constant. Since C99 clause 1 may also be a declaration, and the variable it declares lives in a scope covering the condition, the third clause and the body, ending when the loop ends.
The third clause is not the last line of the body; it sits between the body and the next test, which is what lets a for put the loop's whole shape where a reader can check it in one glance. When a slot has to do two things, the comma operator sequences them left to right and yields the right-hand value, so i++, j-- fits where a single expression is required. Use that to keep everything governing the loop in the header and everything the loop exists to do in the body: a header that mutates state the condition never reads has stopped describing the loop.
placeholder
<stdio.h>
static int init(void) { puts("init"); return 0; }
static int test(int i) { printf("test %d\n", i); return i < 2; }
static int step(int i) { printf("step %d -> %d\n", i, i + 1); return i + 1; }
int main(void)
{
for (int i = init(); test(i); i = step(i))
printf(" body %d\n", i);
return 0;
}
A for header is three separately scheduled clauses - run once, tested before each iteration, run after each finished body - not a single counting instruction.
Worked examples
Two indices in one header
Reversing a string in place, with a declaration in the first clause and the comma operator in the third.
<stdio.h>
<string.h>
int main(void)
{
char s[] = "stressed";
for (int i = 0, j = (int)strlen(s) - 1; i < j; i++, j--) {
char t = s[i];
s[i] = s[j];
s[j] = t;
}
printf("%s\n", s);
return 0;
}
Example explained
Line 1int i = 0, j = (int)strlen(s) - 1 is one declaration with two declarators, so that comma is punctuation, not the comma operator.
Line 2strlen sits in the first clause, which runs once; in the condition it would be called again before every iteration.
Line 3i++, j-- is the comma operator: the left operand is evaluated and discarded, then the right, fitting two updates into a slot that holds one expression.
Line 4The condition i < j is still a single expression, and the loop stops as soon as the indices meet, so a middle character is never swapped with itself.
Empty clauses and the counter's scope
A header with nothing in the first and third slots, followed by a counter whose name dies with its loop.
<stdio.h>
int main(void)
{
int n = 0;
for (; n * n < 200; )
n += 7;
printf("n = %d\n", n);
for (int n = 0; n < 3; n++)
printf("inner n = %d\n", n);
printf("outer n is still %d\n", n);
return 0;
}
Example explained
Line 1Both semicolons remain in for (; n * n < 200; ); the empty slots simply mean nothing is evaluated at those two points.
Line 2The condition need not compare a counter to a limit - n * n < 200 is just an expression - and here the advance happens in the body instead.
Line 3int n in the second header declares a fresh variable scoped to that loop, hiding the outer n inside the header and body.
Line 4The last printf sees the outer n again, still 21, which is the price of declaring a counter in the header: nothing after the loop can read it.
Important notes
Only the condition gets a default: an omitted second clause is treated as a nonzero constant, so for (;;) never ends on its own, while omitted first and third clauses mean nothing happens at those moments.
The names declared in the header can be shadowed by declarations inside the body, so reusing the same counter name in a header nested within the body creates a second, unrelated variable.
Common mistakes
Writing for (int i = 0; i < n; i++); - the stray semicolon is the entire body, so the loop spins doing nothing and the block below it executes once with the counter already spent.
Separating the clauses with commas, as in for (i = 0, i < n, i++) - that is one expression, and the compiler rejects the header for missing its semicolons rather than guessing what you meant.
Advancing the counter both in the third clause and inside the body, which steps twice per pass and silently visits only half the values.
Try it yourself
Change, predict, then run
Print every power of two below 1000 using a for loop whose third clause is p *= 2, then rewrite the same loop with an empty third clause and the doubling at the end of the body, and confirm both print the same line.
Open the C workspaceCheck your understanding
A for loop's condition calls a function that prints each time it is evaluated, and the body always runs to completion. If the body runs three times, how many times is the condition evaluated?
- Three, because each completed body is paired with one test
- Four, because the condition is evaluated before every attempt to run the body and the final attempt fails
- Three, because the loop exits from the third clause once the counter passes the limit, so no further test happens
- Four, because the condition runs once before the loop starts and then once inside each body
Show answer
A for loop can only end when an evaluation of the condition yields zero, so three completed bodies mean three passing tests plus one failing one: four evaluations. Option 0 is tempting because test and body feel paired, but the failing test has no body after it. Option 2 misplaces the exit: the third clause only changes state, and it is the next test that reads that state and stops the loop.