Control flow · about 40 min

switch and multi-way choices

Why this matters

Programs constantly dispatch on a small set of values: a command letter typed by the user, a message type in a network protocol, an opcode in an emulator, a state in a state machine. You can write all of these with if / else if, but switch says "pick one of these fixed cases" directly, is easier to scan, lets the compiler warn about mistakes, and often compiles to a single indexed jump instead of a chain of comparisons. It also has one famous trap, fall-through, that you need to understand rather than memorize.

The shape of a switch

switch (expression) {
case CONSTANT1:
    statements
    break;
case CONSTANT2:
    statements
    break;
default:
    statements
}

The expression is evaluated once. Control then jumps to the case label whose constant equals that value, or to default if none matches. If there is no match and no default, nothing in the switch runs.

#include <stdio.h>

int main(void)
{
    char cmd;
    if (scanf(" %c", &cmd) != 1)
        return 1;
    switch (cmd) {
    case 'n':
        printf("new file\n");
        break;
    case 'o':
        printf("open file\n");
        break;
    case 'q':
        printf("quit\n");
        break;
    default:
        printf("unknown command '%c'\n", cmd);
        break;
    }
    return 0;
}

(The space before %c in the format tells scanf to skip any whitespace, such as a leftover newline, before reading the character; %c alone reads the very next byte, whatever it is.)

$ echo o | ./prog
open file
$ echo x | ./prog
unknown command 'x'

Rules:

Labels, not compartments: fall-through

The key to understanding switch is that case labels are just labels, places to jump to. They do not end anything. After jumping to a label, execution continues straight down through the following statements, including those under later labels, until it hits break (which leaves the switch), return, or the closing brace. Continuing into the next case is called fall-through.

int day = 2;
switch (day) {
case 1:
    printf("Mon\n");
case 2:
    printf("Tue\n");
case 3:
    printf("Wed\n");
    break;
default:
    printf("?\n");
}
Tue
Wed

Day 2 jumps to case 2, prints Tue, falls into case 3, prints Wed, then break exits. Forgetting a break is the classic switch bug, so gcc warns (with -Wextra):

b.c:10:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
   10 |         printf("Tue\n");
      |         ^~~~~~~~~~~~~~~
b.c:11:5: note: here
   11 |     case 3:
      |     ^~~~

Fall-through is occasionally useful, most commonly to give several values the same code by stacking labels with nothing between them. gcc does not warn about this form:

switch (c) {
case 'a': case 'e': case 'i': case 'o': case 'u':
    printf("vowel\n");
    break;
default:
    printf("not a vowel\n");
}

When you deliberately fall through after doing some work, say so with a comment such as /* fall through */ (gcc recognizes it and stays quiet). C23 adds [[fallthrough]]; for the same purpose.

What the compiler does with a switch

Because all labels are constants known at compile time, the compiler can choose the fastest strategy. For a few scattered values it emits compare-and-jump instructions, like an if chain. For many values packed close together (say 0 to 20), it often builds a jump table: an array of code addresses indexed by the value.

switch (op) with cases 0..4

  jump table:          code:
  [0] -> L0            L0: ... break
  [1] -> L1            L1: ... break
  [2] -> L2            L2: ... break
  [3] -> Ldefault      Ldefault: ...
  [4] -> L4            L4: ... break

  check 0 <= op <= 4, otherwise go to Ldefault
  jump to table[op]          <- one jump, however many cases

That makes switch a natural fit for interpreters and emulators, which dispatch on an instruction code millions of times per second.

switch or if?

Use switch when you are comparing one integer expression against several constant values. Use if / else if for ranges (score >= 90), for floating-point values, for strings, or when each branch tests something different. Some compilers offer case ranges like case 1 ... 5:, but that is a GCC extension, not standard C.

Variables declared inside a case

A label must be followed by a statement, and in C17 a declaration is not a statement. Writing int y = 3; directly after case 1: is accepted by GCC 14 as an extension, but -pedantic reports:

warning: a label can only be part of a statement and a declaration is not a statement [-Wpedantic]

The portable fix is to give the case its own block, which also limits the variable's scope to that case:

case 1: {
    int y = 3;
    printf("%d\n", y);
    break;
}

Common mistakes

Recap

Going further: write a switch with cases 0 to 9 and compile with gcc -O2 -S. Look for a jmp * instruction and a table of .long entries: that is the jump table.

Exercises

Exercise 1 · quiz

Predict the fall-through

What does this print?

int level = 2;
switch (level) {
case 1:
    printf("one ");
case 2:
    printf("two ");
case 3:
    printf("three ");
    break;
case 4:
    printf("four ");
}
printf("\n");
Answer
  • two three

switch jumps to the matching label, case 2, and then simply keeps executing statements in order. Labels do not stop execution; only break (or return, or the end of the switch) does. So it prints "two ", falls through into case 3, prints "three ", and the break leaves the switch. gcc's -Wimplicit-fallthrough (part of -Wextra) warns about every fall-through like this.

Exercise 2 · write code

A four-function calculator

Read an expression of the form integer operator integer (for example 12 * 4) using scanf("%d %c %d", &a, &op, &b), and print the result as an integer on one line. Use a switch on the operator.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int a, b;
    char op;
    if (scanf("%d %c %d", &a, &op, &b) != 3) {
        printf("error: bad input\n");
        return 1;
    }
    /* switch on op here */
    return 0;
}

Example

input

12 * 4

expected output

48

Example

input

7 - 10

expected output

-3
Hint 1

Case labels can be character constants: case '+':.

Hint 2

Put break; at the end of every case, and use default: for unknown operators.

Hint 3

Inside case '/':, check b == 0 with an if before dividing.

Reference solution
#include <stdio.h>

int main(void)
{
    int a, b;
    char op;
    if (scanf("%d %c %d", &a, &op, &b) != 3) {
        printf("error: bad input\n");
        return 1;
    }
    switch (op) {
    case '+':
        printf("%d\n", a + b);
        break;
    case '-':
        printf("%d\n", a - b);
        break;
    case '*':
        printf("%d\n", a * b);
        break;
    case '/':
        if (b == 0) {
            printf("error: division by zero\n");
            return 1;
        }
        printf("%d\n", a / b);
        break;
    default:
        printf("error: unknown operator '%c'\n", op);
        return 1;
    }
    return 0;
}

Exercise 3 · quiz

What can you switch on?

Which of these can be the controlling expression of a switch in C? Select all that apply.

Answer
  • An int variable

  • A char variable

  • An expression like n % 3

switch works only on integer types (char is a small integer), and case labels must be integer constant expressions known at compile time. A double gives "error: switch quantity not an integer". Strings cannot be compared with switch at all; you will compare them with strcmp in an if/else chain once you meet strings.

Exercise 4 · write code

Days in a month

Read a month number from 1 to 12 and print how many days it has in a non-leap year, as just the number (for example 31). February has 28. April, June, September and November have 30. The rest have 31.

Use one switch in which months with the same answer share a single printf by stacking case labels (no repeated printf calls for 30 and 31). For any other number print invalid month and exit with status 1.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int month;
    if (scanf("%d", &month) != 1)
        return 1;
    switch (month) {
    case 1:
        printf("31\n");
        break;
    /* ... */
    }
    return 0;
}

Example

input

1

expected output

31

Example

input

2

expected output

28
Hint 1

Several labels can precede one group of statements: case 4: case 6: case 9: case 11:.

Hint 2

An empty case (a label followed directly by another label) is the one kind of fall-through gcc does not warn about.

Hint 3

Do not forget default: for numbers outside 1 to 12.

Reference solution
#include <stdio.h>

int main(void)
{
    int month;
    if (scanf("%d", &month) != 1)
        return 1;
    switch (month) {
    case 2:
        printf("28\n");
        break;
    case 4: case 6: case 9: case 11:
        printf("30\n");
        break;
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
        printf("31\n");
        break;
    default:
        printf("invalid month\n");
        return 1;
    }
    return 0;
}