switch
A long if chain that compares one value with a list of constants is a switch written the hard way. The statement jumps straight to the matching case, which is clearer and often faster. The controlling value must have an integer type; strings and floating-point values cannot label a case.
switch on an integer
Each case takes a constant expression of the same integer type. Execution jumps to the matching label and then continues from there, so every branch ends with break unless the next branch is meant to run too. The default label catches every value without a case of its own.
#include <stdio.h>
int main(void)
{
int choice = 2;
switch (choice) {
case 1:
printf("one\n");
break;
case 2:
printf("two\n");
break;
case 3:
printf("three\n");
break;
default:
printf("unknown\n");
break;
}
return 0;
}
gcc -Wall -Wextra menu.c -o menu
./menu
two
The break ends the whole statement, not just the case. Without it, the program prints every message from the matching label down to the end of the switch, which is the first mistake in this lesson.
Grouping cases
Several labels can share one block: a label with no statements falls into the next one, and the group runs once. This is the clean way to test membership without a chain of comparisons. The example walks a short run of character codes and classifies each one.
#include <stdio.h>
int main(void)
{
for (int c = 'a'; c <= 'e'; c++) {
switch (c) {
case 'a':
case 'e':
printf("%c is a vowel\n", c);
break;
case 'b':
case 'c':
case 'd':
printf("%c is a consonant\n", c);
break;
default:
printf("%c is something else\n", c);
break;
}
}
return 0;
}
gcc -Wall -Wextra groups.c -o groups
./groups
a is a vowel
b is a consonant
c is a consonant
d is a consonant
e is a vowel
The empty labels are not a trick: they are the standard spelling of "or" in a switch. A comment such as /* a and e */ above the group saves the next reader a second look.
Deliberate fallthrough
A case without break continues into the next one, which can be useful when each step adds to the same result. The flags are the bits from lesson 11; a higher level repeats every lower permission, so the cases deliberately fall through. The comment tells the compiler and the reader that it is intended.
#include <stdio.h>
int main(void)
{
for (int level = 1; level <= 3; level++) {
unsigned int flags = 0;
switch (level) {
case 3:
flags |= 4u; /* execute */
/* fall through */
case 2:
flags |= 2u; /* write */
/* fall through */
case 1:
flags |= 1u; /* read */
break;
default:
break;
}
printf("level %d: flags %u\n", level, flags);
}
return 0;
}
gcc -Wall -Wextra fallthrough.c -o fallthrough
./fallthrough
level 1: flags 1
level 2: flags 3
level 3: flags 7
GCC recognises the exact comment /* fall through */ and stays quiet. Without it, -Wextra reports the missing break, because in most code a fallthrough is a bug.
Switching on an expression
The controlling value can be any integer expression, evaluated once before the jump. A common use is a remainder or a computed code, which turns a range test into a small set of cases. The expression is not re-evaluated inside the cases.
#include <stdio.h>
int main(void)
{
int value = 17;
switch (value % 3) {
case 0:
printf("divisible by three\n");
break;
case 1:
printf("remainder one\n");
break;
case 2:
printf("remainder two\n");
break;
}
return 0;
}
gcc -Wall -Wextra expr.c -o expr
./expr
remainder two
There is no default here because the remainder of a division by three is always 0, 1 or 2. When every value is covered, a default would only hide a future change of the divisor.
Missing break and duplicate case
The first program forgets a break after the first case. It compiles, prints both messages, and only the warning points at the line where execution runs on:
#include <stdio.h>
int main(void)
{
int command = 1;
switch (command) {
case 1:
printf("start\n");
case 2:
printf("stop\n");
break;
default:
printf("unknown\n");
}
return 0;
}
gcc -Wall -Wextra nobreak.c -o nobreak
./nobreak
nobreak.c: In function 'main':
nobreak.c:9:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
9 | printf("start\n");
| ^~~~~~~~~~~~~~~~~
nobreak.c:10:5: note: here
10 | case 2:
| ^~~~
start
stop
The second program repeats a label. This one does not compile at all, because two cases would be indistinguishable:
#include <stdio.h>
int main(void)
{
int value = 2;
switch (value) {
case 1:
printf("one\n");
break;
case 2:
printf("two\n");
break;
case 1:
printf("one again\n");
break;
}
return 0;
}
gcc -Wall -Wextra duplicate.c -o duplicate
duplicate.c: In function 'main':
duplicate.c:14:5: error: duplicate case value
14 | case 1:
| ^~~~
duplicate.c:8:5: note: previously used here
8 | case 1:
| ^~~~
The note names the first use, which is the fastest way to find the copy. A switch also rejects case labels that are not integer constants, so a variable or a string cannot label a case.
Exercises
- Turn the grade chain from lesson 12 into a switch on score / 10 and check every boundary score.
- Write a switch with a deliberate fallthrough that builds a permission mask from a level, then print the mask for levels 0 to 4.
- Add a menu loop that reads a number with scanf (lesson 10) and exits on 0. What happens to the newline after each number?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Which type can a
switchcontrol expression have?Answer
An integer type
Case labels must be integer constant expressions, so the controlling value has an integer type; strings and floating-point values are rejected.
-
What does
breakdo inside aswitch?Answer
It leaves the switch statement
breakends the whole switch, which is why each independent branch needs one. -
What is the effect of several
caselabels with no statements between them?Answer
The labels share the following block
A label without statements falls into the next one, so a group of labels runs one shared block.
-
Is a
defaultlabel required in aswitch?Answer
No, it is optional
defaultcatches values without a matching case, but a switch where every value is covered does not need one. -
What does this program print?
#include <stdio.h> int main(void) { int choice = 2; switch (choice) { case 1: printf("one\n"); break; case 2: printf("two\n"); break; case 3: printf("three\n"); break; default: printf("unknown\n"); break; } return 0; }Answer
two
Execution jumps to
case 2, and the break stops it before the next label. -
What does this program print for level 3?
#include <stdio.h> int main(void) { int level = 3; unsigned int flags = 0; switch (level) { case 3: flags |= 4u; /* fall through */ case 2: flags |= 2u; /* fall through */ case 1: flags |= 1u; break; default: break; } printf("flags %u\n", flags); return 0; }Answer
flags 7
Level 3 sets execute, then falls through and adds write and read, giving 4 plus 2 plus 1.
-
What does this program print?
#include <stdio.h> int main(void) { int value = 17; switch (value % 3) { case 0: printf("divisible by three\n"); break; case 1: printf("remainder one\n"); break; case 2: printf("remainder two\n"); break; } return 0; }Answer
remainder two
17 divided by 3 leaves 2, so execution jumps to the case for remainder two.
-
case 1printsstartand has nobreak, thencase 2printsstop. What is printed?Answer
start and then stop
After the first message execution continues into
case 2, prints the second message, and stops at the break. -
What happens when two
caselabels in one switch have the same value?Answer
The compiler reports a duplicate case value
Two identical labels would be indistinguishable, so the compiler rejects the file instead of choosing one.
-
This program is built with
gcc -std=c17 -Wall -Wextra nobreak.c -o nobreak. What does GCC warn about?#include <stdio.h> int main(void) { int command = 1; switch (command) { case 1: printf("start\n"); case 2: printf("stop\n"); break; default: printf("unknown\n"); } return 0; }Answer
this statement may fall through [-Wimplicit-fallthrough=]
The first case has no break, so execution runs into the second one and GCC warns about the possible fallthrough.
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