C / BRANCHING AND SWITCH
switch statements and intentional fallthrough
Use switch fallthrough on purpose: stack labels to share a body, cascade cases that share their trailing work, and mark each deliberate fallthrough.
What you will learn
- Read a switch body as one labeled block, so control runs past case labels by default.
- Stack case labels with no statements between them to share one body.
- Build a cascade only when cases share their trailing work, ordered largest first.
- Mark every deliberate fallthrough with /* fall through */ or [[fallthrough]];
Understanding switch statements and intentional fallthrough
The body of a switch is a single compound statement, and each case label is nothing more than a named entry point inside it. The controlling expression picks which label control jumps to; from that point execution continues straight ahead through the following statements, crossing later case labels as if they were not there. That is why fallthrough is not a special feature you switch on: it is the ordinary behaviour of a block, and break is the thing that interrupts it.
Intentional fallthrough shows up in two shapes. The first is stacked labels with no statements between them, as in case 'a': case 'b': letters++; break; — every reader parses that as one body serving several values, and no compiler warns about it. The second shape is a case that runs its own statements and then deliberately continues into the next label; from the outside that is textually identical to a forgotten break, so it needs a marker: a /* fall through */ comment, which GCC's -Wimplicit-fallthrough recognises, or the C23 attribute [[fallthrough]];, which Clang wants.
A cascade only works when the cases share a suffix of their work. In a permission table, an admin does everything an editor does plus more, so putting the admin case first and letting it fall into the editor case expresses "and also" with no duplicated statements and no helper function. The price is that behaviour now depends on source order: moving a case, or inserting a new one in the middle, silently changes what other values do. Keep cascades short, order them by one obvious rule such as descending numbers or increasing privilege, and if a case needs the shared work before its own extra work, extract a function instead, because fallthrough can only add work after the label you entered.
<stdio.h>
enum role { VIEWER, EDITOR, ADMIN };
static void show(enum role r)
{
printf("role %d:", (int)r);
switch (r) {
case ADMIN:
printf(" delete-users");
/* fall through */
case EDITOR:
printf(" edit-posts");
/* fall through */
case VIEWER:
printf(" read-posts");
break;
}
putchar('\n');
}
int main(void)
{
show(ADMIN);
show(EDITOR);
show(VIEWER);
return 0;
}A switch body is one block with labels, so fallthrough is the default behaviour, and it lets neighbouring cases share the tail of their work.
Worked examples
Stacked labels, one body
Several values share a single statement list because nothing sits between their case labels.
<stdio.h>
int main(void)
{
const char *s = "a3 b!";
int letters = 0, digits = 0, other = 0;
for (const char *p = s; *p != '\0'; p++) {
switch (*p) {
case 'a': case 'b': case 'c':
letters++;
break;
case '0': case '1': case '2': case '3':
digits++;
break;
default:
other++;
break;
}
}
printf("letters=%d digits=%d other=%d\n", letters, digits, other);
return 0;
}Example explained
Line 1case 'a': case 'b': case 'c': are three entry points into the same statement, so there is nothing between them to fall through.
Line 2Because the labels share one body there is exactly one letters++ and one break covering all three characters.
Line 3The space and the '!' match no label, so control enters default: and other ends at 2.
Line 4No fallthrough warning is possible in this switch: a compiler only complains when statements execute before control crosses the next case label.
Copying a 0-to-3 byte tail
A descending cascade performs "n copies" with no loop and no counter test.
<stdio.h>
static void copy_tail(char *dst, const char *src, int n)
{
switch (n) {
case 3: *dst++ = *src++; /* fall through */
case 2: *dst++ = *src++; /* fall through */
case 1: *dst++ = *src++; /* fall through */
case 0: break;
}
}
int main(void)
{
char buf[8];
for (int n = 0; n <= 3; n++) {
for (int i = 0; i < 7; i++)
buf[i] = '.';
buf[7] = '\0';
copy_tail(buf, "XYZ", n);
printf("n=%d buf=%s\n", n, buf);
}
return 0;
}Example explained
Line 1Entering at case 3 copies one byte and then continues into case 2, so n == 3 performs three copies from a single jump.
Line 2The labels descend because control only ever moves forward; the largest count must be the highest label to reach the most work.
Line 3*dst++ = *src++ advances both pointers, so each label copies the next byte instead of repeating the same one.
Line 4case 0: break; is both the landing spot for n == 0 and the single exit shared by every other path.
Important notes
GCC's -Wimplicit-fallthrough accepts a /* fall through */ comment as the marker; Clang does not, so under -Werror there you need [[fallthrough]]; or __attribute__((fallthrough));.
Reaching the closing brace of the switch from the last case is not fallthrough into anything and needs no marker, but leaving the break there anyway costs nothing and survives someone appending a case later.
Common mistakes
Ordering the cascade upward: writing case 1 first and expecting case 3 to pick up its work. Control only moves forward through the block, so tier 3 runs its own line and nothing else, and the totals come out short.
Adding a new case in the middle of an existing cascade. Its statements are now executed by every value that enters above it, so an unrelated tier silently gains a feature or a charge.
Declaring and initialising a variable inside one case and using it in a case below. Entering at the lower label skips the initialiser, leaving an indeterminate value; before C23 a declaration directly after a label is also rejected outright.
Try it yourself
Change, predict, then run
Write int features(int tier) that prints one line per feature using a single break: tier 3 prints api and falls into tier 2's export, which falls into tier 1's dashboard. Then add a tier 4 printing sso above case 3, and confirm tiers 1 to 3 still print exactly the lines they printed before.
Open the C workspaceCheck your understanding
A switch has case 3 adding 100, falling into case 2 adding 50, falling into case 1 adding 10 followed by the only break. A teammate moves the whole case 1 block, break included, to the top of the switch and changes nothing else. What happens?
- Nothing changes, because a switch enters only the label that matches, so the order of the cases cannot affect the result.
- Tier 1 still gives 10, but tier 3 now gives 150 and tier 2 gives 50, because the shared +10 no longer sits below them.
- The compiler rejects the switch, because case labels must appear in ascending order for fallthrough to be legal.
- Every tier now gives 10, because the first case written in a switch is the one that runs first.
Show answer
Entry is decided by the matching label, but after that control simply runs forward through the block, so a case can only reach work written below it. With case 1 and its break moved above case 3, tiers 3 and 2 hit the end of the switch after their own additions and lose the +10, while tier 1 is unaffected. The first option is tempting because the entry point really does depend only on the value, but what runs after entry is pure textual order, which is exactly what the move changed.