The Concept of Selection
Control flow determines the path execution takes through a program. In C, branching is achieved primarily through if-else constructs and switch statements. Unlike high-level languages with dedicated bool types, C treats logic as a numerical property.
Truthiness: The Zero Rule
In C, there is no native boolean type in the core language (prior to C99’s <stdbool.h>). The rules for truth are simple:
- 0 (Zero) is False. This applies to integers, floating-point numbers, and the
NULLpointer. - Anything Non-Zero is True. This includes negative numbers.
if (5) { /* This will always execute */ }
if (0) { /* This will never execute */ }
if (-1) { /* This will execute! */ }
The if-else Construct
The if statement evaluates an expression. If it is non-zero, the following block executes.
Dangling Else Problem
When nesting if statements, an else always associates with the nearest preceding if that doesn’t have an else. This can lead to logic errors if braces are omitted.
if (a > 0)
if (b > 0)
do_thing();
else // This else belongs to (b > 0), not (a > 0)!
do_other_thing();
Best Practice: Always use curly braces {} to avoid ambiguity and improve maintainability.
The switch Statement and Jump Tables
The switch statement is used for multi-way branching based on an integer constant.
switch (expression) {
case CONSTANT_1:
// statement
break;
case CONSTANT_2:
// statement
break;
default:
// statement
}
Performance: why use Switch?
When a switch has many cases, the compiler often optimizes it using a Jump Table. Instead of checking every condition sequentially (as in an if-else if chain), the CPU can jump directly to the correct code block using an offset in an array of addresses. This makes switch statements in terms of time complexity in many scenarios.
Fall-through Behavior
Unlike modern languages (like Swift or Go), C cases “fall through” by default. If you omit the break keyword, execution continues into the next case. This is occasionally useful for mapping multiple inputs to the same output:
Fall-through Logic
switch(input) { case 'y': case 'Y': confirmed = 1; ; case 'n': confirmed = 0; break; }
Branch Prediction and Performance
Modern CPUs use Branch Predictors to guess the outcome of if statements before they are fully evaluated. This allows the CPU to pre-fetch instructions.
- Predictable Branches: If a branch is almost always true (e.g., checking for errors that rarely occur), the CPU stays fast.
- Mispredictions: If a branch’s outcome is random (e.g., processing unsorted data), the CPU must flush its pipeline when it guesses wrong, leading to a significant performance hit.
Interactive Lab
Waiting for signal...