Why this matters
A program that always does the same thing is a calculator with one button. Real programs react: reject bad input, take a different path when a file is missing, charge shipping only below a certain order total. In C, every such decision comes down to the if statement and an expression that is either zero or not. Understanding exactly how C decides "true" also explains a few classic bugs that have shipped in real software.
The if statement
if (condition)
statement
The condition is any expression in parentheses. If its value is nonzero, the statement runs; if it is zero, the statement is skipped. To run several statements, group them in a block with braces; a block counts as one statement:
#include <stdio.h>
int main(void)
{
int temp;
if (scanf("%d", &temp) != 1)
return 1;
if (temp > 30) {
printf("It is hot.\n");
printf("Drink water.\n");
}
printf("Have a nice day.\n");
return 0;
}
$ echo 35 | ./prog
It is hot.
Drink water.
Have a nice day.
$ echo 20 | ./prog
Have a nice day.
What "true" means in C
C has no special truth type at the machine level. A condition is true if it is nonzero and false if it is zero. Comparison operators such as > and == produce the int value 1 when they hold and 0 when they do not:
printf("%d %d\n", 3 > 2, 3 > 5); /* prints: 1 0 */
So if (n) means "if n is not zero", and if (!n) means "if n is zero". (C99 added _Bool and the header <stdbool.h> with bool, true and false, but they are still the numbers 1 and 0 underneath.)
Underneath, the compiler turns an if into a comparison followed by a conditional jump: an instruction that moves the program counter past the guarded code when the condition is false.
compare temp with 30
jump-if-less-or-equal to SKIP <- condition false: jump over the block
call printf("It is hot.")
call printf("Drink water.")
SKIP:
call printf("Have a nice day.")
else and else if
else gives the alternative:
if (n % 2 == 0)
printf("even\n");
else
printf("odd\n");
For more than two outcomes, chain them. Each else if is tested only if every earlier condition was false, and at most one branch runs:
int score = 83;
if (score >= 90) {
printf("A\n");
} else if (score >= 80) {
printf("B\n"); /* this one runs */
} else if (score >= 70) {
printf("C\n"); /* not tested: an earlier branch already ran */
} else {
printf("F\n");
}
Order matters. If you tested score >= 70 first, an 83 would get a C. There is no special elseif keyword: else if is simply an else whose statement happens to be another if.
Conditions combine with && (and), || (or) and ! (not), which the previous module covered, including short-circuit evaluation:
if (age >= 18 && age < 65)
printf("working age\n");
The conditional operator ?:
Sometimes you want to choose a value rather than run different statements. The conditional operator (also called the ternary operator, because it takes three operands) is an expression:
condition ? value_if_true : value_if_false
int a = 7, b = 12;
int larger = (a > b) ? a : b; /* 12 */
int n = 1;
printf("%d item%s\n", n, n == 1 ? "" : "s"); /* 1 item */
It is perfect for small choices like this inside a larger expression. For anything with side effects or more than one level of nesting, use if: nested ?: is legal but hard to read.
Braces and the dangling else
Without braces, an if controls exactly one statement. Indentation means nothing to the compiler. Two consequences bite everyone eventually.
First, adding a second line "inside" an unbraced if:
if (balance < 0)
printf("Overdrawn!\n");
charge_fee(); /* ALWAYS runs: not part of the if */
gcc catches some of these with -Wmisleading-indentation. A famous Apple security bug in 2014 ("goto fail") was exactly this pattern: a duplicated line that ran unconditionally and skipped certificate verification.
Second, the dangling else. An else attaches to the nearest unmatched if:
int x = 5;
if (x > 0)
if (x > 10)
printf("A\n");
else /* belongs to if (x > 10), despite the indentation */
printf("B\n");
This prints B. gcc warns:
warning: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]
The cure for both is the same habit: put braces on every branch unless the whole if fits on one line.
Common mistakes
Assignment instead of comparison. = assigns, == compares:
if (x = 5) /* sets x to 5, then tests 5: always true */
warning: suggest parentheses around assignment used as truth value [-Wparentheses]
A stray semicolon after the condition. The semicolon is an empty statement, which becomes the body of the if; the next line always runs:
if (x > 100);
printf("big\n"); /* always printed */
warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
Chained comparisons. 0 < x < 10 is not a range test. It is (0 < x) < 10, and 0 < x is 0 or 1, which is always less than 10:
warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
Write 0 < x && x < 10.
Comparing floating point with ==. As the types module showed, 0.1 + 0.2 == 0.3 is false. Compare against a small tolerance instead.
Recap
if (cond) stmtrunsstmtwhencondis nonzero;elseprovides the alternative.- In C, zero is false and everything else is true; comparisons yield 1 or 0.
- In an
if/else if/elsechain the conditions are tested in order and at most one branch runs. cond ? a : bchooses between two values inside an expression.- An unbraced
ifcontrols one statement;elsebinds to the nearestif. Use braces. - Watch for
=vs==, a stray;afterif (...), anda < b < c. Warnings catch all three.
Going further: compile one of the examples with gcc -S -O0 and find the cmp instruction followed by a conditional jump such as jle or jne. Then look up <stdbool.h> and rewrite a condition using a bool variable.