Step 1 of 5 · Reading · ~4 min
Learn
Control Flow
Conditionals
C's if looks like every language that copied it — which is all of them — but two of its original design choices are live ammunition: any number is a condition, and = fits where == belongs. Both compile silently. Both have specific defenses. Learn the defenses with the syntax.
The shape
if (score >= 90) {
printf("A\n");
} else if (score >= 80) {
printf("B\n");
} else {
printf("F\n");
}
Parentheses around the condition: required. Braces: technically optional for single statements — write them anyway. The famous goto fail bug (Apple, 2014, TLS verification bypassed) was a brace-less if gaining an accidental second line. Every style guide since says the same thing: always brace.
Note the shape of the chain as a whole. else if links tests so that exactly one branch runs, and a final bare else is what makes "one branch runs" true for every input rather than most of them. A chain that ends on its last else if quietly does nothing for anything that matched none of the tests — no output, no error, no clue.
Truthiness, C-style
C (pre-C99) didn't even have a bool type: zero is false, everything else is true. Comparisons return the int 1 or 0.
if (n) /* means: if n != 0 */
if (!n) /* means: if n == 0 */
if (strcmp(a, b) == 0) /* strcmp returns 0 on EQUAL — write the == 0! */
Idiomatic C leans on this (if (ptr) for "pointer isn't NULL" is universal), but it's also why the strcmp trap from Strings bites: if (strcmp(a, b)) is true for different strings — reads like "if equal," means the opposite. House rule for clarity: bare truthiness for pointers and flags, explicit comparison (== 0, != 0) whenever a function's return value has meaning beyond yes/no.
The assignment-in-condition trap
if (x = 5) { … } /* assigns 5 to x, then tests 5 → always true */
if (x == 5) { … } /* what you meant */
One = missing, no error, condition always true — the single most storied bug in C. Defenses, in order of effectiveness: compile with -Wall (it warns on exactly this), and let your editor highlight it. Some veterans write "Yoda conditions" (if (5 == x)) so the typo'd version fails to compile; modern consensus is that warnings make that style unnecessary — but now you can read it in old code without blinking.
switch: one value, many constants
switch (grade) { /* integers, chars and enums — never strings */
case 1:
printf("one "); /* no break: execution FALLS THROUGH */
case 2:
printf("two ");
case 3:
printf("three ");
break; /* stops here */
default:
printf("other ");
}
/* grade == 2 prints: two three */
switch compares one integer-valued expression against constant case labels — you cannot switch on a string, and strcmp inside an if/else if chain is C's answer when you want to. The design choice that surprises everyone is in the comment above: a case does not end where the next one begins. Control jumps to the matching label and then keeps running until it meets a break or falls out of the block, which is why matching case 2 printed two words. Deliberate fall-through is occasionally exactly right — stacking case 'a': case 'e': so several values share one body — so put a break on every case until you specifically mean otherwise.
Logical operators and short-circuit
if (n > 0 && 100 / n > 5) /* && stops if n <= 0 — division never runs */
if (a || b) /* || stops at the first true */
&& and || short-circuit: the right side only evaluates if needed. That's not trivia — it's a guard idiom: test-before-use in one line (ptr != NULL && ptr->value > 0), relied on by every C codebase alive. ! negates. Precedence tip that ends debugging sessions: && binds tighter than ||; parenthesize anything mixed.
C also has the ternary — grade = (score >= 60) ? 'P' : 'F'; — compact for simple picks, unreadable when nested; use accordingly.
Your exercise: FizzBuzz for one number
The eternal kata: multiple of 3 → Fizz, of 5 → Buzz, both → FizzBuzz, else the number itself. Two edges, and the exercise is nothing but edges. Test the "both" case first (n % 15 == 0), because a 3-then-5 chain classifies 15 as Fizz and stops. And finish the chain with a plain else that prints the number — a three-branch version is silently wrong for every input that is neither a multiple of 3 nor of 5, and the examples you can see all happen to land in a Fizz or Buzz branch, so they will not warn you. Chain: 15, then 3, then 5, then printf("%d\n", n).
And since this is C: == in every condition, braces on every branch, -Wall mentally on.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…