Control Flow
Control flow is an integral part of any computer program. They allow use to change which parts of a program run at runtime. C features three main categories of control flow, the first being if
statements and its extensions which are the most common type of control flow used in C. The other two are switch statements and the ternary operator which provide slightly different semantics to their if
counterparts.
if
Statements
if
statements are the most primitive form of control flow in programming. In essence, some block of code is isolated from the rest of the program, protected by some Boolean expression. If the Boolean expression evaluates as truth then the block of code is executed. In C the keyword if
is used to introduce an if
clause. This is the part of the statement that contains a Boolean expression (called a redicate) which is evaluated on arrival. The rest of the if
statement is a block of code nested in braces which only executes when the if
clause is true
.
#include <stdio.h>
int main()
{
int a = 4;
if (a > 5)
{
puts("a > 5");
}
return 0;
}
What do you think the output of the above code is?
else
Statements
Often an if
statement on its own is not enough because there will always be two potential outcomes of the Boolean predicate the true
and the false
branches and we will often want to handle the case when the predicate fails. This is where an else
statement comes in. else
statements have no predicate clause as it is bound to the alternative outcome of an if
clause. C uses the else
keyword to introduce the else
statement which is just another code block surrounded in braces.
#include <stdio.h>
int main()
{
int a = 4;
if (a > 5)
{
puts("a > 4");
}
else
{
puts("a <= 4");
}
return 0;
}
Note:
The placement of braces in C is not strict ie. the above can be written as:
#include <stdio.h> int main() { int a = 4; if (a > 5) { puts("a > 4"); } else { puts("a <= 4"); } return 0; }
else-if
Clauses
C allows use to extend the usage of else
statements with additional if
clauses. This allows you to form an else-if
clause which allows you to test multiple predicates and select only one block of code to execute. These additional clauses are called branches of the program as the line of execution can differ depending on runtime conditions.
#include <stdio.h>
int main()
{
int a = 4;
if (a > 5)
{
puts("a > 4");
}
else if (a == 4)
{
puts("a == 4");
}
else
{
puts("a < 4");
}
return 0;
}
Note:
Inefficient usage of branching constructs can cause massive slow downs of many systems at large scales due to a CPU level optimisation called branch prediction which tries to 'predict' which branch is most likely to occur and load the instructions corresponding to its code block into the cache ahead of time. However, a large number of branches increases the chance of these algorithms being incorrect which can lead to a cache miss which involves the CPU having to wipe the cache of the prefetched instructions and then lookup and load the correct instructions which can be expensive if the branching code runs a lot.
switch
Statements
The other main control flow construct in C is called the switch
statement. These take an integral value and matches it against a particular case
for which it is equal and executes the corresponding code block. While similar are to if
statements, switch
statements differ in a subtle way. switch
statements allow for fallthrough which means that the line of execution will continue through different cases if the switch
statement is not broken out of using a break
statement. The most common use of switch
statements is with enums as they allow you to use an enum to represent a finite list of possible states and handle each case accordingly. switch
statements can also have a default case to handle any uncaught matches.
#include <stdio.h>
enum cmp_result_t { UNDEF, EQUAL, LESS, GREATER };
int main()
{
int a = 4;
cmp_result_t cmp_r = UNDEF;
if (a > 5)
{
cmp_r = GREATER;
}
else if (a == 4)
{
cmp_r = EQUAL;
}
else
{
cmp_r = LESS;
}
switch (cmp_r)
{
case EQUAL:
puts("equal");
break;
case LESS:
puts("less");
break;
case GREATER:
puts("greater");
break;
default:
puts("NaN");
break;
}
return 0;
}
Ternary Operator
The final control flow construction is the ternary operator. This is a condensed if
statement that is able to return a value. It involves a Boolean predicate followed by two expressions that may return or have side effects (ie. print something). The ternary operator comprises of the symbol ?:
where ?
is used to separate the predicate and branches and :
is used to separate the branches.
#include <stdio.h>
int main()
{
int a = 4;
a > 4 ? puts("a > 4") : puts("a <= 4");
int b = a > 4 ? a + 5 : a * 100;
printf("%d\n", b);
return 0;
}