Loops
C has three loop statements, and the choice between them is about where the control lives. for keeps initialisation, condition and update in one line, while tests before the body, and do-while tests after it. Leaving a loop early with break and goto is the next lesson.
for
A for header has three expressions separated by semicolons: the initialisation, the condition and the update. The initialisation runs once, the condition is tested before each iteration, and the update runs after the body. The loop variable declared in the header is visible only in the loop.
#include <stdio.h>
int main(void)
{
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
printf("sum: %d\n", sum);
for (int i = 1; i <= 3; i++) {
printf("%d squared is %d\n", i, i * i);
}
return 0;
}
gcc -Wall -Wextra forloop.c -o forloop
./forloop
sum: 55
1 squared is 1
2 squared is 4
3 squared is 9
The sum 1 + 2 + ... + 10 is 55, and the loop reaches it with ten iterations. Changing <= to < would run nine times, which the last section shows.
while
Use while when the number of iterations is not known in advance and the body must not run at all if the condition is false at the start. The loop below removes digits from a number until nothing is left, adding each digit on the way.
#include <stdio.h>
int main(void)
{
int number = 4729;
int digit_sum = 0;
while (number > 0) {
digit_sum += number % 10;
number /= 10;
}
printf("digit sum: %d\n", digit_sum);
return 0;
}
gcc -Wall -Wextra whileloop.c -o whileloop
./whileloop
digit sum: 22
Every iteration must move the value toward the end of the loop, here toward zero. A body that leaves the condition unchanged produces the endless loop from the last section, which no compiler flag catches.
do-while
A do-while runs its body before the first test, so it always executes at least once. That suits a menu or a retry, where the first attempt is unconditional. The condition follows while and needs a semicolon of its own.
#include <stdio.h>
int main(void)
{
int attempts = 0;
do {
attempts++;
printf("attempt %d\n", attempts);
} while (attempts < 0);
printf("after the loop: %d\n", attempts);
return 0;
}
gcc -Wall -Wextra dowhile.c -o dowhile
./dowhile
attempt 1
after the loop: 1
The condition is false from the start, and the body still ran once. With while instead of do-while the counter would stay at zero and nothing would print.
Nested loops
A loop inside a loop runs the whole inner loop for every pass of the outer one, so the work multiplies. Printing a triangle uses the outer variable as the bound of the inner loop, which makes the row length grow by one each time.
#include <stdio.h>
int main(void)
{
for (int row = 1; row <= 4; row++) {
for (int col = 1; col <= row; col++) {
putchar('*');
}
putchar('\n');
}
return 0;
}
gcc -Wall -Wextra nested.c -o nested
./nested
*
**
***
****
The outer loop runs four times and the inner loop runs 1 + 2 + 3 + 4 = 10 times in total. Keep the loop variables distinct; reusing i in both loops is a classic way to break the outer count.
An endless loop and an off-by-one
An unsigned counter can never be negative, so i >= 0 is always true. The loop below would run forever and print decreasing values after the wrap; GCC reports the condition before the program ever starts:
#include <stdio.h>
int main(void)
{
for (unsigned int i = 5; i >= 0; i--) {
printf("%u\n", i);
}
return 0;
}
gcc -Wall -Wextra unsignedloop.c -o unsignedloop
unsignedloop.c: In function 'main':
unsignedloop.c:5:32: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]
5 | for (unsigned int i = 5; i >= 0; i--) {
| ^~
Counting down in an unsigned type needs a different stop condition, for example i > 0 with the body handling zero separately, or a signed counter. The second mistake is quieter: the comparison uses < where the range needs <=, so the last value never contributes.
#include <stdio.h>
int main(void)
{
int sum = 0;
for (int i = 1; i < 5; i++) {
sum += i;
}
printf("sum: %d\n", sum);
sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
printf("sum: %d\n", sum);
return 0;
}
gcc -Wall -Wextra offbyone.c -o offbyone
./offbyone
sum: 10
sum: 15
The first loop adds 1 to 4, the second adds 1 to 5. No warning is possible here, because both loops are correct C; only the intended range decides which one is right.
Exercises
- Print the multiplication table from 1 to 5 with nested loops, one row per line and tab-separated products.
- Compute the sum of the digits of a number with a while loop, then with a for loop that has an empty update expression.
- Print the first ten values of the Collatz sequence starting at 27 and count the steps until it reaches 1. Use a while loop and no arrays.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
In a
forheader, when does the update expression run?Answer
After each iteration of the body
Initialisation runs once, the condition is tested before each pass, and the update runs after the body.
-
Which loop always runs its body at least once?
Answer
do-whiledo-whiletests its condition after the body, so the first pass is unconditional. -
How many times does the inner body run in two nested loops of 3 and 4 iterations?
Answer
12
The whole inner loop runs for every pass of the outer one, so the work multiplies: 3 times 4 is 12.
-
Why is
for (unsigned int i = 5; i >= 0; i--)wrong?Answer
An unsigned value is never below zero
An unsigned counter wraps to a large value instead of becoming negative, so the condition stays true forever.
-
What does this program print?
#include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i <= 10; i++) { sum += i; } printf("%d\n", sum); return 0; }Answer
55
The loop includes both ends, and 1 plus 2 through 10 is 55.
-
What does this program print?
#include <stdio.h> int main(void) { int number = 4729; int digit_sum = 0; while (number > 0) { digit_sum += number % 10; number /= 10; } printf("%d\n", digit_sum); return 0; }Answer
22
The loop adds 9, 2, 7 and 4, which makes 22.
-
What does this program print?
#include <stdio.h> int main(void) { int attempts = 0; do { attempts++; printf("attempt %d\n", attempts); } while (attempts < 0); printf("after the loop: %d\n", attempts); return 0; }Answer
attempt 1 / after the loop: 1
The body runs before the first test, so it prints once even though the condition is false from the start.
-
What does this program print?
#include <stdio.h> int main(void) { int stars = 0; for (int row = 1; row <= 4; row++) { for (int col = 1; col <= row; col++) { stars++; } } printf("stars: %d\n", stars); return 0; }Answer
stars: 10
The rows hold 1, 2, 3 and 4 stars, which add up to 10.
-
What does this program print?
#include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i < 5; i++) { sum += i; } printf("%d\n", sum); return 0; }Answer
10
The condition stops before 5, so the sum is 1 plus 2 plus 3 plus 4.
-
This program is built with
gcc -std=c17 -Wall -Wextra unsignedloop.c -o unsignedloop. Which warning flag does GCC print?#include <stdio.h> int main(void) { for (unsigned int i = 5; i >= 0; i--) { printf("%u\n", i); } return 0; }Answer
-Wtype-limits
The unsigned comparison against zero is always true, which
-Wtype-limitsreports.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev