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

  1. Turn the grade chain from lesson 12 into a switch on score / 10 and check every boundary score.
  2. Write a switch with a deliberate fallthrough that builds a permission mask from a level, then print the mask for levels 0 to 4.
  3. 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.

  1. Which type can a switch control 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.

  2. What does break do inside a switch?

    Answer

    It leaves the switch statement

    break ends the whole switch, which is why each independent branch needs one.

  3. What is the effect of several case labels 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.

  4. Is a default label required in a switch?

    Answer

    No, it is optional

    default catches values without a matching case, but a switch where every value is covered does not need one.

  5. 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.

  6. 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.

  7. 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.

  8. case 1 prints start and has no break, then case 2 prints stop. 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.

  9. What happens when two case labels 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.

  10. 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