Skip to main content

Control Flow: if, switch, loops, and the for idiom

What This Concept Is

C's control flow is minimal and mostly familiar, but the idioms matter because they are what other C programmers read for.

The core pieces:

  • if (expr) stmt / if (expr) stmt else stmt - any non-zero expr is "true"
  • switch (expr) { case C1: ... break; default: ... } - integer dispatch with implicit fall-through
  • while (expr) stmt - test then body
  • do stmt while (expr); - body then test (runs at least once)
  • for (init; cond; step) stmt - equivalent to init; while (cond) { stmt; step; }
  • break exits the innermost switch or loop; continue jumps to the step of the innermost loop
  • goto label; jumps within a function; used in well-placed error-handling chains

The for idiom in C is structured loop plumbing: initialization, termination test, and increment all in one place, so the shape of the loop is visible at the top.

Why It Matters Here

The rest of the module and the next module lean on two habits:

  1. Write loops with a clear termination argument.
  2. Do not rely on incidental fall-through in switch; document any intentional one.

Off-by-one and accidental fall-through are two of the most common C bugs. Control-flow discipline is where you stop them.

Concrete Example

The canonical for loop over an array:

int arr[N];
for (size_t i = 0; i < N; i++) {
arr[i] = (int)i;
}

switch with an explicit fall-through:

switch (c) {
case ' ':
case '\t':
case '\n':
handle_whitespace();
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
handle_digit(c);
break;
default:
handle_other(c);
break;
}

Reasonable goto for error handling:

int f(void) {
FILE *fp = fopen("x", "r");
if (!fp) goto fail_fp;
char *buf = malloc(N);
if (!buf) goto fail_buf;
/* ... real work ... */
free(buf);
fclose(fp);
return 0;
fail_buf:
fclose(fp);
fail_fp:
return -1;
}

Common Confusion / Misconception

"switch is a cleaner if/else." It is not. Every case without a terminating break falls into the next one, which is both a feature and a constant source of bugs. Always close each non-fall-through case with break or return.

"goto is always wrong." In C, a forward-only goto to a single cleanup block is idiomatic. What is wrong is backward goto used as a substitute for a loop.

How To Use It

  1. Default to for (size_t i = 0; i < n; i++) for counted iteration. Use while when the termination test is the focus, not the counter.
  2. Every case either ends with break, return, goto, or a deliberate comment /* fall through */.
  3. Declare the loop variable inside the for when using C99 or later.
  4. Keep the body of each control-flow construct indented; never omit the braces on multi-line bodies.
  5. Use goto only for forward jumps to error-cleanup, and only when the alternative is deeper nesting.

Check Yourself

  1. In for (i = 0; i < n; i++) sum += a[i];, name three ways you could get an off-by-one.
  2. Why does switch require break?
  3. When is do/while the right choice instead of while?

Mini Drill or Application

Write, by hand, without looking them up:

  1. A while loop that reads characters from stdin until EOF using getchar and counts newlines.
  2. A for loop that sums arr[0..n-1] into long long sum and avoids signed overflow.
  3. A switch on a char that classifies vowels, consonants, digits, whitespace, and other.
  4. An error-handling pattern using a single goto cleanup; target that releases two resources.

Read This Only If Stuck