Control flow#
C’s control surface is small and C-shaped. if / else,
while, do while, for, switch / case,
break / continue / return / goto. There is no
exception flow; the operator either returns a code or uses
setjmp / longjmp (see Errors) for the rare
cases where unwinding by hand is impossible.
For
break inside switch and the fall-through trap, read the
switch section carefully.
Conditional#
if / else if / else. The condition is any
expression; non-zero is true, zero is false.
flowchart TD
A([start]) --> B{x > 0?}
B -->|true| C[positive]
B -->|false| D{x == 0?}
D -->|true| E[zero]
D -->|false| F[negative]
C --> Z([end])
E --> Z
F --> Z
if (x > 0) {
positive();
} else if (x == 0) {
zero();
} else {
negative();
}
C has the ternary expression for inline branches.
int abs = (x < 0) ? -x : x;
Useful when the branch fits one expression.
const char *label = (n % 2 == 0) ? "even" : "odd";
The operator always braces single-statement blocks; if (x)
a(); plus a later “let me add a second line” is the original
Apple goto fail family of bug.
Loops#
For#
flowchart TD
A([init]) --> B{cond?}
B -->|true| C[body]
C --> D[step]
D --> B
B -->|false| E([end])
C-style: init, condition, step.
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
Pointer-walk over a linked list.
for (node_t *p = head; p != NULL; p = p->next) {
visit(p);
}
While#
flowchart TD
A([start]) --> B{cond?}
B -->|true| C[body]
C --> B
B -->|false| D([end])
Test-first.
while (queue_len(q) > 0) {
process(queue_pop(q));
}
Do-while#
flowchart TD
A([start]) --> B[body]
B --> C{cond?}
C -->|true| B
C -->|false| D([end])
Test-after; runs the body at least once.
do {
line = read_line(stdin);
handle(line);
} while (strcmp(line, "quit") != 0);
C has no range-style iteration; write the
counter or pointer walk explicitly.
Switch#
switch dispatches on an integer expression (int,
char, enum). Cases fall through by default; the
operator writes break on every case to stop that.
flowchart TD
A([switch state]) --> B{case IDLE?}
B -->|yes| C[start]
B -->|no| D{case RUNNING?}
D -->|yes| E[step]
D -->|no| F{case DONE?}
F -->|yes| G[cleanup]
F -->|no| H[default]
C --> Z([break out])
E --> Z
G --> Z
H --> Z
switch (state) {
case STATE_IDLE:
start();
break;
case STATE_RUNNING:
step();
break;
case STATE_DONE:
cleanup();
break;
default:
fprintf(stderr, "bad state: %d\n", state);
return -1;
}
When fall-through is deliberate, the operator marks it with a
comment (or with the C23 [[fallthrough]] attribute) so
-Wimplicit-fallthrough does not complain.
switch (kind) {
case A:
case B:
handle_ab();
break;
case C:
prepare();
[[fallthrough]]; /* C23 attribute */
case D:
finish();
break;
}
Case labels accept only integer constants (no ranges in
standard C; GCC extension allows 1 ... 5).
Jumps#
Keyword |
Effect |
|---|---|
|
Exit the innermost loop or |
|
Skip the rest of the loop body, jump to the next iteration. |
|
Exit the current function with a value (or void). |
|
Jump to a named label in the same function. Common use is cleanup-on-failure. |
The cleanup pattern.
int run(void) {
FILE *f = fopen(path, "r");
if (!f) return -1;
char *buf = malloc(SIZE);
if (!buf) { fclose(f); return -1; }
if (process(f, buf) < 0) goto err;
/* success path */
free(buf);
fclose(f);
return 0;
err:
free(buf);
fclose(f);
return -1;
}
goto to a cleanup label avoids nested if ladders and is
the standard kernel-style pattern in C.