Modern C: Everything is about control
Chapter 3 of Jens Gustedt’s Modern C: A Guide to the C23 Standard, titled “Everything is about control,” shifts focus from basic syntax and declarations to managing the execution path (control flow) of a program. While function calls provide unconditional transfers of control, C provides five primary conditional control statements: if, for, do, while, and switch.
Here is a detailed breakdown of the most important concepts, structural rules, and takeaways from this chapter:
1. Conditional Execution: if and if-else (Section 3.1)
The if construct executes a secondary block of code based on whether a controlling expression evaluates to true or false. Adding an else branch creates a selection statement that directs execution down one of two distinct code paths.
- Numerical Truth Rules:
- Takeaway 3.1 #1: The value
0represents logicalfalse. - Takeaway 3.1 #2: Any value different from
0represents logicaltrue.
- Takeaway 3.1 #1: The value
- Scalar Truth Values:
- Takeaway 3.1 #4: All scalars have a truth value. Numerical types (
size_t,int,double,bool) and pointer types are scalar types, meaning any scalar variable can be passed directly as a controlling condition.
- Takeaway 3.1 #4: All scalars have a truth value. Numerical types (
- Clean Boolean Testing:
- Takeaway 3.1 #3: Don’t compare to
0,false, ortrue. Writingif (b == true)orif (x != 0)is redundant and clutters code. Instead, test boolean or scalar expressions directly (e.g.,if (b)orif (x)orif (!x)).
- Takeaway 3.1 #3: Don’t compare to
Example
In C, controlling conditions evaluate scalar expressions directly.
- Takeaway 3.1 #1 & #2:
0represents logicalfalse; any non-zero value represents logicaltrue. - Takeaway 3.1 #4: All scalars (integers, floats, pointers, booleans) have an inherent truth value.
- Takeaway 3.1 #3: Don’t compare directly to
0,false, ortrue(e.g., writeif (b)instead ofif (b == true)).
#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
void demo_conditional_execution(void) {
bool flag = true;
size_t count = 42;
double const* ptr = nullptr; // C23 null pointer constant
// GOOD PRACTICE (Takeaway 3.1 #3): Test boolean expressions directly
if (flag) {
printf("Flag is set (clean boolean test)\n");
}
// BAD PRACTICE (Avoid!): if (flag == true) or if ((flag != false) == true)
// Redundant comparisons clutter code and add visual noise.
// SCALAR TRUTH VALUES (Takeaway 3.1 #1, #2, #4):
// Integer count (42 != 0) evaluates to true directly
if (count) {
printf("Count is non-zero (%zu) -> evaluates to true\n", count);
}
// Testing logical negation (!ptr evaluates to true when ptr is nullptr)
if (!ptr) {
printf("Pointer is null -> !ptr evaluates to true\n");
}
// if-else selection statement branching
size_t threshold = 50;
if (count > threshold) {
printf("Count exceeds threshold\n");
} else {
printf("Count (%zu) is within threshold (%zu)\n", count, threshold);
}
}2. Iterations: for, while, and do-while (Section 3.2)
C provides three construct types to repeat execution over a domain or until a condition is met:
- Domain Iteration (
for):- Structure:
for (clause1; condition2; expression3) secondary-block. clause1initializes or defines the loop variable (scope is narrowly restricted to the loop).condition2tests if iteration should continue before each pass.expression3updates the loop variable at the end of each iteration.foris the preferred tool for domain iterations where bounds are known.
- Structure:
- Pre-Test Condition Iteration (
while):- Structure:
while (condition) secondary-block. - Evaluates the condition before running the body. If the condition starts as
false, the secondary block executes zero times.
- Structure:
- Post-Test Condition Iteration (
do-while):- Structure:
do secondary-block while (condition);. - Runs the secondary block unconditionally at least once before evaluating the condition. Syntactically, it requires a terminating semicolon
;after the condition.
- Structure:
- Loop Control Statements:
break: Immediately terminates the loop, skipping the rest of the body and the condition check.continue: Skips the remainder of the current loop body and jumps directly to the condition re-evaluation (or loop variable update infor).- Infinite Loop Idiom:
for (;;)is equivalent towhile (true).
Example
C provides three primary iteration constructs:
for: Preferred for domain iterations where bounds or iteration counts are known.while: Pre-tested loop; evaluates the condition before executing the body (may execute 0 times).do-while: Post-tested loop; executes the body at least once before testing.
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
void demo_iterations(void) {
// 1. DOMAIN ITERATION (for loop)
// Loop variable 'i' is declared inside the initial clause, restricting its scope to the loop.
// 'i' counts down from 5 to 1. The condition 'i' implicitly checks (i != 0).
printf("1. Countdown using 'for': ");
for (size_t i = 5; i; --i) {
printf("%zu ", i);
}
printf("\n");
// 2. PRE-TEST CONDITION ITERATION (while loop)
// Checks condition before each iteration. Runs 0 or more times.
size_t energy = 3;
printf("2. Energy drainage using 'while': ");
while (energy) { // Evaluates energy != 0
printf("[%zu remaining] ", energy);
--energy;
}
printf("\n");
// 3. POST-TEST CONDITION ITERATION (do-while loop)
// Runs secondary block AT LEAST ONCE before evaluating condition.
// Syntactically requires a terminating semicolon ';' after while(cond);.
size_t attempts = 0;
printf("3. Process loop using 'do-while': ");
do {
++attempts;
printf("(Attempt %zu executed) ", attempts);
} while (attempts < 1); // Condition checked after body execution
printf("\n");
// 4. BREAK AND CONTINUE CONTROL
printf("4. Loop with break and continue: ");
for (size_t k = 1; k <= 10; ++k) {
if (k % 2 == 0) {
continue; // Skip even numbers, jumping straight to ++k update
}
if (k > 7) {
break; // Terminate loop completely when k exceeds 7
}
printf("%zu ", k); // Prints 1 3 5 7
}
printf("\n");
}3. Multiple Selection: switch (Section 3.3)
The switch statement selects one of several code execution paths based on an integer value, replacing tedious cascades of if-else blocks.
- Jump Targets and Fall-Through:
caselabels anddefaultact as jump targets inside theswitchblock.- When a matching
caseis found, control jumps there. Execution continues sequentially down into subsequentcaseblocks (fall-through) unless explicitly stopped by abreakstatement. - The
defaultlabel executes if nocasematches the controlling expression.
- Strict Constraints on
caseLabels:- Takeaway 3.3 #1:
casevalues must be integer constant expressions. Values must be fixed at compile time (e.g., literal numbers like4or character constants like'm'); variables cannot be used ascasevalues. - Takeaway 3.3 #2:
casevalues must be unique within a singleswitchstatement. - Takeaway 3.3 #3:
caselabels must not jump beyond a variable definition.
- Takeaway 3.3 #1:
Example
The switch statement handles multi-way branching based on integer expressions. case labels act as jump targets; execution falls through into subsequent case blocks unless stopped by a break statement.
- Takeaway 3.3 #1:
casevalues must be integer constant expressions (ICE) known at compile time. - Takeaway 3.3 #2:
casevalues must be unique within a singleswitchstatement. - Takeaway 3.3 #3:
caselabels must not jump beyond a variable definition.
#include <stdio.h>
void demo_multiple_selection(char corvid_code) {
// Controlling expression 'corvid_code' is evaluated once at entry
switch (corvid_code) {
// Takeaway 3.3 #1: 'm', 'r', 'j', 'c' are character constants (integer values)
// Takeaway 3.3 #2: Each case value is unique
case 'm':
puts("Selected: Magpie");
break; // Prevents fall-through, exiting the switch
case 'r':
puts("Selected: Raven");
break;
case 'j':
case 'J': // Intentional grouping: both 'j' and 'J' fall through to the same action
puts("Selected: Jay");
break;
case 'c': {
// Takeaway 3.3 #3: Enclose block in braces {} when defining local variables
// to ensure case labels do not jump over variable initializations!
int chough_id = 42;
printf("Selected: Chough (ID: %d)\n", chough_id);
break;
}
default:
puts("Selected: Unknown corvid bird");
break;
}
}Summary Table of Control Constructs in C
| Construct | Type | Evaluation Timing | Best Used For |
|---|---|---|---|
if / if-else | Selection | Evaluated before entering branch | Binary decision paths based on scalar conditions |
for | Iteration | Pre-tested before each loop pass | Counting/bounded domain iterations with defined loop variables |
while | Iteration | Pre-tested before each loop pass | Loops running 0 or more times based on a state condition |
do-while | Iteration | Post-tested after each loop pass | Loops that must execute at least once (e.g., user input/convergence) |
switch | Selection | Evaluated once at entry | Multi-way branching based on fixed integer/character constant values |