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:
- The controlling expression must have an integer type:
int,char,long,unsigned, and so on, including enums (later in the track). Notdouble, not strings. - Each case label must be an integer constant expression: something the compiler can compute, like
3,'q'or2 * 8. Not a variable. - No two labels may have the same value (
error: duplicate case value). defaultmay appear anywhere, but by convention it goes last.
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
- Missing
break. The next case runs too. Read-Wimplicit-fallthroughwarnings. - Switching on a double:
error: switch quantity not an integer. - A variable as a case label:
case limit:giveserror: case label does not reduce to an integer constant. - Forgetting
default. Not an error, but unexpected values then do nothing silently. Even when "it cannot happen", a default that prints an error makes the impossible visible. - Using
breakto leave a loop from inside a switch. Inside a switch,breakleaves the switch, not the surrounding loop. You will meet this again in the lesson onbreakandcontinue.
Recap
switchevaluates an integer expression once and jumps to the matchingcaselabel or todefault.- Case labels are integer constants and must be unique.
- Labels do not stop execution: without
break, control falls through into the next case.-Wimplicit-fallthroughwarns about it. - Stacked labels (
case 4: case 6:) share code without a warning. - Compilers may turn dense switches into jump tables: one indexed jump instead of many comparisons.
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.