student@ubuntu:~$
c-foundations Lesson 4 12 min read

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.

Reading: Hanly & Koffman: §4.1–4.5 (pp. 174–206), §5.1–5.7 (pp. 236–276)

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 / else chains and say why you always use braces
  • Use switch with correct break placement and intentional fall-through
  • Choose between for, while, and do/while based on the loop’s exit condition
  • Apply break and continue, and predict what continue does inside a for
  • Write C90-style for loops 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: both ifs false; total becomes 1.
  • i = 2: both false; total becomes 3.
  • i = 3: first if true, continue; step clause advances i to 4.
  • i = 4: second if true, 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

Select every statement that is true about C control flow.
AWriting for (int i = 0; i < n; i++) is a C99 feature and fails under -std=c90 -pedantic.
BA switch in C works on int, char, double, and string values.
Ccontinue in a for loop runs the step clause before the next condition check.
DMissing braces on a multi-statement if body caused the Apple "goto fail" CVE; -Wmisleading-indentation (on by default via -Wall) catches the most common form.
EA do/while body can run zero times if the condition is initially false.
FStacking case labels like case 'F': case 'f': is a legal way to share a single body between two values.
Correct: A, C, D, F.
  • B is wrong: switch does not accept double or string values, only integral types.
  • E is wrong: do/while checks 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.