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.
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 to x, then tests 5 (nonzero = true)
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.
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.
break and continue
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).
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 */
} 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.
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.
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 over a variable declaration that has an initializer (in C99+).
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("Wind of Change\n"); break;
case 3: printf("Rock You Like a Hurricane\n"); break;
default: printf("Unknown track\n"); break;
}
return 0;
}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.
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.