If/Else, Switch & Loops
Branching, switch fall-through, loops, and the indentation bug that sank Apple SSL
Based on content from Dr. Stu Steiner, Eastern Washington University.
In a nutshell
The control flow in C is almost exactly the control flow you used in Java: decide (if), pick (switch), repeat (for, while, do/while), plus break and continue. The one C-specific wrinkle worth knowing early is switch fall-through, which is a real bug when you forget a break and a real feature when you want two case labels to share a body.
Practice this topic: C Control Flow drill, or browse the practice gallery.
After this lesson, you will be able to:
- Write
if/else if/elsechains and say why you always use braces - Use
switchwith correctbreakplacement and intentional fall-through - Choose between
for,while, anddo/whilebased on the loop’s exit condition - Apply
breakandcontinue, and predict whatcontinuedoes inside afor - Write C90-style
forloops with the counter declared at the top
Quick reference
| Shape | Use when |
|---|---|
if (cond) { ... } else if (cond2) { ... } else { ... } |
Branching on arbitrary conditions |
switch (x) { case 1: ... break; default: ... } |
Branching on a single int or char against constants |
for (i = 0; i < n; i++) { ... } (C90) |
Known number of iterations |
while (cond) { ... } |
Repeat until a condition becomes false; body may run zero times |
do { ... } while (cond); |
Body must run at least once |
break; |
Exit the enclosing loop or switch |
continue; |
Skip to the next iteration of the enclosing loop |
Coming from CSCD 210
if/else, for, while, do/while, switch, break, continue all exist in Java and work the same way. Three C-specific points: switch falls through without break (Java does the same, but C’s warning infrastructure is different); the for counter must be declared at the top of the block in C90; and missing braces around a multi-line body are the subject of CWE-483 and one of the most famous CVEs of the decade.
Branching
if / else if / else
Same shape as Java. The condition is any expression; zero is false, non-zero is true.
int score = 85;
if (score >= 90) {
printf("A\n");
} else if (score >= 80) {
printf("B\n");
} else if (score >= 70) {
printf("C\n");
} else {
printf("F\n");
}
C has no elif keyword: it is else if, two words, which under the hood is just a nested if in the else block.
Always use braces, even when the body is one statement. The bug you are preventing produced CVE-2014-1266, Apple’s “goto fail” vulnerability:
if ((err = do_check()) != 0)
goto fail;
goto fail; /* this runs unconditionally */
if ((err = another_check()) != 0)
goto fail;
Indentation suggests the second goto fail; is inside the if. It is not. Without braces, only the first statement belongs to the if. For nearly two years every iPhone silently accepted forged SSL certificates because of this. GCC catches the pattern with -Wmisleading-indentation (on via -Wall). Braces catch it universally. The mechanism is detailed in the memory-safety deep-dive.
Check your understanding (what does this print?)
#include <stdio.h>
int main(void)
{
int x = 5;
if (x > 10)
printf("A\n");
printf("B\n");
printf("done\n");
return 0;
}
Reveal answer
B
done
Only the single statement after if (the first printf) is part of the if body. The second printf("B\n"); is standalone and always runs. x > 10 is false, so A is not printed, but B is. This is the “goto fail” pattern in miniature.
switch
switch works on int and char values only. Not strings, not double.
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent\n");
break;
case 'B':
printf("Good\n");
break;
case 'C':
printf("Average\n");
break;
default:
printf("Unknown\n");
break;
}
Each case ends with break. Forgetting break causes fall-through: execution continues into the next case until it hits a break or the closing brace.
Fall-through is the whole feature in Lab 1’s class-standing section, where F and f both mean Freshman:
char standing;
scanf(" %c", &standing);
switch (standing) {
case 'F': case 'f':
printf("Standing: Freshman\n");
break;
case 'O': case 'o':
printf("Standing: Sophomore\n");
break;
case 'J': case 'j':
printf("Standing: Junior\n");
break;
case 'S': case 's':
printf("Standing: Senior\n");
break;
default:
printf("Standing: Unknown\n");
break;
}
Two labels stacked mean “this body runs for either value.” Lab 1’s rubric accepts either a switch like this or an if/else if chain using ||.
Looping
for in C90 (counter at the top)
int i; /* declared at the top of the enclosing block */
for (i = 0; i < 5; i++) {
printf("%d ", i);
}
/* Output: 0 1 2 3 4 */
C90 requires the counter declaration to be outside the for. Writing for (int i = 0; ...) is a C99 feature and fails under -std=c90 -pedantic. Lab 1 and Quiz 1 both check this.
Anatomy: for (init; condition; step) { body; } runs init once, then repeatedly: check condition, run body, run step.
while and do/while
while checks first, then runs. The body may run zero times.
int n = 1;
while (n <= 100) {
n *= 2;
}
printf("%d\n", n); /* 128 */
do/while runs first, then checks. The body always runs at least once. Useful for input validation:
int input;
do {
printf("Enter a positive number: ");
scanf("%d", &input);
} while (input <= 0);
One do/while-specific trap: the while (cond); line ends with a semicolon. Two ways this goes wrong, back to back:
/* Bug 1: accidental semicolon turns the while into an empty infinite loop.
The braces below are a separate compound statement that never runs. */
while (keep_going);
{
do_work();
}
/* Bug 2: missing semicolon after a do/while condition is a compile error. */
do {
do_work();
} while (keep_going)
Flow control inside loops
break and continue
break exits the nearest enclosing loop or switch. continue skips to the next iteration of the nearest enclosing loop.
int i;
/* Print odd numbers 1 through 9. */
for (i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
}
continue in for vs while
continue in a for loop jumps to the step clause, then the condition check, then the body. The step clause still runs after a continue, which is usually what you want.
int i;
for (i = 0; i < 10; i++) {
if (i == 3) {
continue; /* i still gets incremented; next iter is i == 4 */
}
/* work */
}
In a while loop, continue jumps to the condition check without running anything that increments the counter. That creates an infinite loop if the increment is in the body after the continue:
int i = 0;
while (i < 10) {
if (i == 3) {
continue; /* i is NEVER incremented; infinite loop */
}
i++;
}
When using continue, prefer for, or put the increment above any continue.
Check your understanding (predict the output)
#include <stdio.h>
int main(void)
{
int i;
int total = 0;
for (i = 1; i <= 5; i++) {
if (i == 3) {
continue;
}
if (i == 4) {
break;
}
total += i;
}
printf("total = %d\n", total);
return 0;
}
Reveal answer
total = 3.
i = 1: bothifs false;totalbecomes1.i = 2: both false;totalbecomes3.i = 3: firstiftrue,continue; step clause advancesito4.i = 4: secondiftrue,break; loop ends.
5 is never reached.
Nested loops and compound assignment
A loop inside a loop. The inner loop runs to completion for each iteration of the outer.
int row, col;
for (row = 1; row <= 5; row++) {
for (col = 1; col <= 5; col++) {
printf("%4d", row * col);
}
printf("\n");
}
break inside the inner loop exits only the inner loop. Re-factoring into a function and returning is almost always cleaner than using a flag.
Compound assignments shorten “update a variable based on itself”:
| Operator | Same as | Example |
|---|---|---|
sum += x |
sum = sum + x |
sum += 5; |
diff -= x |
diff = diff - x |
diff -= 3; |
prod *= x |
prod = prod * x |
prod *= 2; |
quot /= x |
quot = quot / x |
quot /= 4; |
rem %= x |
rem = rem % x |
rem %= 10; |
You have already been using i++, which is i += 1.
Check yourself and what comes next
- B is wrong:
switchdoes not acceptdoubleor string values, only integral types. - E is wrong:
do/whilechecks the condition after the body, so the body always runs at least once.
Next, Functions & Recursion shows how to organize code into functions and how the call stack handles recursion. Drill this page: C Control Flow or the practice gallery.