5 Control Flow
Control flow in C will feel very familiar if you are coming from C++. The if, while, for, and switch statements work mostly the same way. The differences are small but worth knowing: C has no range-based for loops, no structured bindings, and no std::optional. In C89, all variable declarations had to appear at the top of a block before any statements — C99 relaxed this and let you declare variables anywhere, which is how you are used to writing code in C++.
The biggest conceptual difference is that C has no native bool type. Conditions are just integers: zero is false, and any nonzero value is true.
5.1 if / else
The if statement in C is identical to C++:
int score = 85;
if (score >= 90) {
printf("Excelente!\n");
} else if (score >= 70) {
printf("Passing\n");
} else {
printf("Try again\n");
}The condition in an if is an integer expression. Zero means false, nonzero means true. There is no built-in bool type in C89. C99 added _Bool and the convenience header <stdbool.h>, which defines bool, true, and false:
#include <stdbool.h>
bool is_hurricane = true;
if (is_hurricane) {
printf("Here I am\n");
}Without <stdbool.h>, C programmers traditionally use int for boolean values and 0/1 for false/true, or define their own macros.
Trap: Because conditions are just integers, assignments inside if are legal and a common source of bugs:
int x = 0;
if (x = 5) { // BUG: assigns 5, then tests 5
printf("oops\n"); // always prints
}The compiler may warn you about this, but it will not stop you. If you mean to compare, use ==. Compiling with -Wall helps catch these.
5.2 while and do-while
A while loop tests the condition before executing the body. If the condition is false from the start, the body never executes:
int countdown = 5;
while (countdown > 0) {
printf("%d... ", countdown);
countdown--;
}
printf("Vamos!\n");
// 5... 4... 3... 2... 1... Vamos!A do-while loop tests after the body, guaranteeing at least one iteration. This is useful when you need to perform an action before you can check whether to continue:
#include <stdio.h>
int main(void) {
int choice;
do {
printf("1) Rock 2) Paper 3) Scissors 0) Quit\n");
printf("Enter choice: ");
scanf("%d", &choice);
printf("You picked %d\n", choice);
} while (choice != 0);
printf("Adios!\n");
return 0;
}The semicolon after while (choice != 0) is required — forgetting it is a syntax error.
Tip: Use do-while when the loop body must execute at least once. Menu loops and input validation loops are classic use cases. If you find yourself duplicating code before a while loop just to set up the first test, a do-while is probably cleaner.
5.3 break and continue
Sometimes you realize partway through a loop that you want to stop early or skip ahead. Perhaps you want to break out of the loop entirely, or maybe you want to skip the rest of the current iteration and continue with the next one. break exits the nearest enclosing loop (or switch) immediately. continue skips the rest of the current iteration and jumps to the next one.
#include <stdio.h>
int main(void) {
/* break example: stop at the first multiple of 7 */
for (int i = 1; i <= 20; i++) {
if (i % 7 == 0) {
printf("Found it: %d\n", i);
break;
}
}
/* continue example: skip odd numbers */
for (int i = 0; i < 10; i++) {
if (i % 2 != 0)
continue;
printf("%d ", i);
}
printf("\n");
// 0 2 4 6 8
return 0;
}break and continue only affect the innermost loop. If you have nested loops and need to break out of an outer loop, you either use a flag variable or, in some cases, goto (discussed later in this chapter).
5.4 for Loops
The for loop has the same structure as in C++:
for (init; condition; update) {
/* body */
}Here is a classic example iterating over an array:
int scores[] = {90, 84, 77, 95, 88};
int n = sizeof(scores) / sizeof(scores[0]);
for (int i = 0; i < n; i++) {
printf("Score %d: %d\n", i + 1, scores[i]);
} C99 allows you to declare the loop variable inside the for statement, just like modern C++. In C89, you had to declare it before the loop:
/* C89 style */
int i;
for (i = 0; i < n; i++) {
printf("%d\n", scores[i]);
}
/* C99 style — preferred */
for (int i = 0; i < n; i++) {
printf("%d\n", scores[i]);
}
Tip: C has no range-based for loop. There is no for (auto x : vec). You always iterate with an index or a pointer. The sizeof(arr) / sizeof(arr[0]) idiom gives you the element count of a stack-allocated array, but it does not work on pointers — a pointer does not carry size information. This is why C functions that take arrays almost always take a separate size parameter.
You can iterate over an array with a pointer instead of an index. This is idiomatic C and worth getting comfortable with:
int nums[] = {10, 20, 30, 40, 50};
int *end = nums + sizeof(nums) / sizeof(nums[0]);
for (int *p = nums; p < end; p++) {
printf("%d ", *p);
}
printf("\n");
// 10 20 30 40 50Any part of the for header can be omitted. Omitting all three creates an infinite loop:
for (;;) {
/* runs forever — use break to exit */
} 5.5 switch Statements
A switch statement selects among multiple cases based on an integer expression. If you have used switch in C++, the syntax is identical:
#include <stdio.h>
int main(void) {
int wind = 5; /* Beaufort scale */
switch (wind) {
case 0:
printf("Calm\n");
break;
case 5:
printf("Fresh breeze\n");
break;
case 12:
printf("Huracan!\n");
break;
default:
printf("Wind level: %d\n", wind);
break;
}
return 0;
}Each case must be an integer constant expression. You cannot use strings, floats, or variables as case labels — only values the compiler can evaluate at compile time.
Wut: In C++, a const int counts as a constant expression, so you can use one as a case label. In C you cannot — even const int LIMIT = 2; is rejected with “case label does not reduce to an integer constant”. Use a #define or an enum constant instead.
Fall-through is the most important thing to understand about switch in C. If you forget a break, execution falls through to the next case. This is sometimes intentional:
char grade = 'B';
switch (grade) {
case 'A':
case 'B':
case 'C':
printf("Passing\n");
break;
case 'D':
case 'F':
printf("Not passing\n");
break;
default:
printf("Invalid grade\n");
break;
}Here, cases 'A', 'B', and 'C' all fall through to the same printf. This is deliberate and a common pattern. But accidental fall-through is a frequent bug:
switch (x) {
case 1:
printf("one\n");
/* oops, forgot break — falls into case 2 */
case 2:
printf("two\n");
break;
}If x is 1, this prints both “one” and “two.”
Trap: Every case should end with break unless you intentionally want fall-through. When you do use fall-through on purpose, add a comment like /* fall through */ so the next person reading the code knows it is deliberate. Some compilers recognize this comment and suppress warnings.
5.6 goto
In C++, you were probably taught to never use goto. In C, goto has a legitimate and widely used role: the cleanup pattern. When a function acquires multiple resources (files, memory, locks), and something goes wrong partway through, goto provides a clean way to release everything in the correct order.
Here is a brief example:
#include <stdio.h>
#include <stdlib.h>
int process(const char *path) {
int status = -1;
FILE *f = fopen(path, "r");
if (!f) return -1;
char *buf = malloc(1024);
if (!buf) goto close_file;
/* ... do work with f and buf ... */
status = 0;
free(buf);
close_file:
fclose(f);
return status;
}If malloc fails, control jumps to close_file, which closes the file that was already opened. Without goto, you would need deeply nested if statements or duplicated cleanup code. The goto cleanup pattern is used extensively in production C code, including the Linux kernel.
Tip: The goto cleanup pattern is covered in more detail in the Odds and Ends chapter. For now, just know that goto in C is not the taboo it is in C++. When used strictly for forward jumps to cleanup labels at the end of a function, it makes resource management clearer, not messier.
goto has two restrictions: you can only jump within the same function, and you cannot jump into the scope of a variably modified type such as a variable-length array. Unlike C++, C does let you jump over an ordinary initialized declaration — but the initializer never runs, so the variable holds garbage at the label.
5.7 Try It: Control Flow Starter
#include <stdio.h>
int main(void) {
/* if / else */
int wind = 74; /* mph */
if (wind >= 74) {
printf("Rock you like a hurricane!\n");
} else if (wind >= 39) {
printf("Tropical storm\n");
} else {
printf("Calm seas\n");
}
/* while */
int n = 1;
while (n <= 5) {
printf("%d ", n);
n++;
}
printf("\n");
/* do-while: repeat until valid input */
int guess;
do {
printf("Guess (1-10): ");
scanf("%d", &guess);
} while (guess < 1 || guess > 10);
printf("You guessed %d\n", guess);
/* for with break and continue */
printf("Even numbers up to 20: ");
for (int i = 1; i <= 100; i++) {
if (i > 20)
break;
if (i % 2 != 0)
continue;
printf("%d ", i);
}
printf("\n");
/* switch */
int track = 3;
printf("Side B, Track %d: ", track);
switch (track) {
case 1: printf("Big City Nights\n"); break;
case 2: printf("Still Loving You\n"); break;
case 3: printf("Rock You Like a Hurricane\n"); break;
default: printf("Unknown track\n"); break;
}
return 0;
}5.8 Key Points
- C’s control flow statements (
if,while,do-while,for,switch) are syntactically identical to C++. - C uses integers for boolean conditions: 0 is false, nonzero is true. Include
<stdbool.h>forbool,true, andfalse(C99+). breakexits the nearest loop orswitch.continueskips to the next iteration.- C has no range-based
forloop. Use index or pointer iteration. switchcases must be integer constants. Watch for accidental fall-through.gotois legitimate in C for the cleanup pattern — forward jumps to release resources in reverse order.
5.9 Exercises
Think about it: C uses
0for false and nonzero for true, while C++ has a built-inbooltype. What practical problems can arise from using integers as booleans? Can you think of a case where a nonzero value that is not1might cause a subtle bug?What does this print?
for (int i = 0; i < 5; i++) { if (i == 3) continue; printf("%d ", i); } printf("\n");What does this print?
int x = 2; switch (x) { case 1: printf("uno "); case 2: printf("dos "); case 3: printf("tres "); break; default: printf("other "); } printf("\n");Where is the bug?
int total = 0; int i; for (i = 0; i < 10; i++); { total += i; } printf("Total: %d\n", total);Calculation: How many times does the body of this loop execute?
int count = 0; int i = 10; do { count++; i--; } while (i > 10);Where is the bug?
int level = 5; if (level = 10) { printf("Max level!\n"); }Write a program that reads integers from the user (using
scanf) until the user enters0. Print the sum and average of all numbers entered (not counting the0). Use ado-whileorwhileloop.