Week 15 · Phase 2 — The Ancestry
Switch statements and the jump-table trick that makes "match against twenty options" a single instruction.
Photo · Arisa Chattasa / Unsplash
switch works the same way.If if/else is asking yes-or-no questions in a row, switch is pressing one of N buttons in an elevator. You have a single value, one of several known options, and a bit of code to run for each. C has a dedicated construct for exactly this — and a quietly impressive optimisation that goes with it.
switch (key) {
case 1:
printf("one\n");
break;
case 2:
printf("two\n");
break;
case 3:
printf("three\n");
break;
default:
printf("unknown\n");
}
One value at the top. A list of cases, each labelled with a constant. A default for "anything else". The break at the end of each case jumps out of the switch; without it, execution would fall through into the next case — which is sometimes useful and frequently a bug source.
switch works only on integer-like values: int, char, enum. It will not work on strings or floating-point numbers — for those you need if/else if.
If you wrote the same logic with if/else if, the chef would compare key against 1, then against 2, then against 3, then maybe fall through to default — three comparisons in the worst case. With a switch, the compiler sees something it can optimise: a value being matched against several known constants. So it builds a jump table — an array of pointers, one per case, indexed by the value itself.
The chef takes key, treats it as an index, looks up entry key in the table — finds the address of the right case — and does a single indirect jump. Total cost: one memory read, one branch. If you had a switch over 256 values, an if chain would average 128 comparisons; the jump table does it in one. This is why switch is sometimes much faster than the equivalent if/else if.
The compiler doesn't always build a jump table — it only does so when the cases are dense and small. For sparse cases (1, 5, 99, 1000), the compiler falls back to a binary search or a chain of comparisons. You can usually trust it to pick wisely.
If you forget the break, execution continues into the next case:
switch (grade) {
case 'A':
case 'B':
case 'C':
printf("passed\n");
break;
case 'D':
case 'F':
printf("failed\n");
break;
}
This is fall-through used deliberately — three different inputs all run the same code. Clean and idiomatic. The accidental version is when you forget a break after one case and execution silently runs into the next. C will not warn you — it cannot tell the difference between intentional and accidental fall-through.
Modern compilers will warn if you turn on -Wimplicit-fallthrough. Some C codebases use a special comment /* fallthrough */ as a marker so the linter knows you meant it. C++17 added [[fallthrough]]; as a real annotation. Whichever you use, be explicit about your intentions.
// minimal command dispatch
switch (cmd) {
case 'h': show_help(); break;
case 'q': quit_app(); break;
case 's': save_file(); break;
case 'o': open_file(); break;
case '\n': break;
default: printf("unknown command: %c\n", cmd);
}
Every text-mode interactive program ever written has something like this somewhere. Vim does. The C shell does. Half the early Unix utilities did. Where a case's body is a single function call, C lets you put it on the same line as the case label. Cleaner.
switch with the same fall-through behaviour. Same trap. Most modern style guides ban it in favour of objects-as-dispatch-tables.fallthrough. Better default.match, which is so powerful it's the main control structure of the language — pattern matching against types, ranges, structures, enums, all in one keyword.The C switch is the ancestor. Every modern multi-way decision construct is some descendant of it.
Inside the inner loops of a deep-learning kernel, you almost never see a switch. Why? Because the chef inside a GPU is even more allergic to branching than the CPU chef. A switch in a CUDA kernel can serialise hundreds of parallel cores onto a single case path — a phenomenon called warp divergence — and your speedup vanishes.
Where you do see switch: in the orchestration code that picks which kernel to launch. "If the dtype is FP16, run this kernel; if FP32, run that kernel; if FP8, run the other." That dispatch is on the CPU, where branching is cheap and the chef has a great predictor. Inside the kernel? Smooth, branch-free arithmetic for every element.
Use switch where the choice is rare. Use branch-free arithmetic where the choice is millions per second.
scanf(" %c", &cmd). Switch on it. Each case calls a printf with a different message. Add a default.cc -S -O2 menu.c. Open menu.s. If your switch was dense (e.g. 1, 2, 3, 4), you'll see a jr, jmp, or br through a table. The chef has built the elevator at compile time.break. Watch what happens. Then add -Wimplicit-fallthrough and try again — the compiler will help.Decisions are useful. Repeating decisions, on slightly different data each time, is more useful still. Most of what programs do is some form of "do this thing, again, until something changes". That's a loop.
Week 16 is The Infinite Loop — while, for, and the patient stamina of computers that never tire.
Photo free under the Unsplash license. Elevator · Arisa Chattasa.