Control flow · about 45 min

Decisions: if, else and the conditional operator

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

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.

Exercises

Exercise 1 · write code

Positive, negative or zero

Read one integer from standard input and print exactly one line:

If the input is not a number, print invalid input and exit with status 1.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1) {
        printf("invalid input\n");
        return 1;
    }
    /* decide here */
    return 0;
}

Example

input

17

expected output

positive

Example

input

-4

expected output

negative
Hint 1

Use an if, an else if, and a final else.

Hint 2

Only one branch of an if / else if / else chain ever runs, so the last else catches exactly the case where n is neither positive nor negative.

Reference solution
#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1) {
        printf("invalid input\n");
        return 1;
    }
    if (n > 0) {
        printf("positive\n");
    } else if (n < 0) {
        printf("negative\n");
    } else {
        printf("zero\n");
    }
    return 0;
}

Exercise 2 · quiz

Which else?

What does this print when x is 5?

if (x > 0)
    if (x > 10)
        printf("A\n");
else
    printf("B\n");
printf("C\n");
Answer
  • B then C

An else always belongs to the nearest unmatched if, no matter how it is indented. So the else pairs with if (x > 10): x > 0 is true, x > 10 is false, so the else runs and prints B. The indentation suggests the opposite, which is exactly why gcc warns "suggest explicit braces to avoid ambiguous 'else'" and why braces on every branch are a good habit.

Exercise 3 · write code

One file, two files

Read a non-negative integer n and print n file or n files, using the singular only when n is exactly 1. Examples:

0 files
1 file
7 files

Use the conditional operator ?: to choose the ending inside a single printf call (the check looks for a ? in your code).

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    printf("%d files\n", n);
    return 0;
}

Example

input

0

expected output

0 files

Example

input

1

expected output

1 file
Hint 1

condition ? a : b evaluates to a when the condition is true and b otherwise.

Hint 2

Print the s with %s and choose between the strings "" and "s".

Reference solution
#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    printf("%d file%s\n", n, n == 1 ? "" : "s");
    return 0;
}

Exercise 4 · write code

Leap years

In the Gregorian calendar a year is a leap year if it is divisible by 4, except years divisible by 100, which are not leap years, except years divisible by 400, which are. So 2024 and 2000 are leap years; 1900 and 2023 are not.

Read a year and print exactly one of:

2024 is a leap year
2023 is not a leap year

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int year;
    if (scanf("%d", &year) != 1)
        return 1;
    if (year % 4 == 0)
        printf("%d is a leap year\n", year);
    else
        printf("%d is not a leap year\n", year);
    return 0;
}

Example

input

2024

expected output

2024 is a leap year

Example

input

2023

expected output

2023 is not a leap year
Hint 1

year % 100 == 0 tests divisibility by 100.

Hint 2

One way: check 400 first (leap), then 100 (not leap), then 4 (leap), else not leap, as an else-if chain.

Hint 3

As a single condition: (year % 4 == 0 && year % 100 != 0) || year % 400 == 0.

Reference solution
#include <stdio.h>

int main(void)
{
    int year;
    if (scanf("%d", &year) != 1)
        return 1;
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
        printf("%d is a leap year\n", year);
    else
        printf("%d is not a leap year\n", year);
    return 0;
}