Control Structures in C¶
Learning Objectives: This material explains how to implement and control the logical functionality of a program in C using control structures. Additionally, it emphasizes the importance of making the code visually clear.
Control structures in C are as follows:
- Program statement: This is required to inform the compiler about the program's structure, i.e., which commands the programmer intends to execute, when, and in what order. In C, program statements are terminated with a semicolon
;
. - Block structure: Code blocks that implement a specific functionality. Blocks are delimited by curly braces, within which program statements are written
{ statement; statement; statement; ... }
. - Conditional statements: For implementing branching using
if
andswitch
. The functionality is enclosed in curly braces. - Loop structures (iteration structures)
for
,while
, anddo-while
constructs. The functionality is enclosed in curly braces.
This part of the lecture material is likely the most straightforward and easiest to grasp in the course, as there are no significant differences in the behavior of these structures in C compared to other programming languages.
Truth Values¶
First, it is essential to understand what is considered true in C, i.e., how the truth value of a program statement is determined. In C, it is defined numerically as follows:
- False if the value is
0
. - True if the value is
anything other than 0
. Now, all non-zero values, including negative 2's complement numbers, are considered true.
Example. After the assignment, the value of the variable would be interpreted as true here
int8_t is_warm = -25;
.int8_t temperature_sensor_reading = -25;
if (temperature_sensor_reading) {
printf("The sensor is active, temperature is being measured.\n");
} else {
printf("The sensor is inactive or malfunctioning.\n");
}
Logical Operators¶
The logical operators we can use to evaluate the truth values of expressions in C are as follows:
- Logical negation
!
- Logical AND
&&
- Logical OR
||
Examples.
uint8_t variable1 = 0;
uint8_t variable2 = 5;
int8_t variable3 = -27;
int8_t result1 = !variable1; // true
int8_t result2 = variable1 && variable2; // false
int8_t result3 = variable2 && variable3; // true
In C, logical operations && (logical AND) and || (logical OR) use short-circuit evaluation. This means that the evaluation of expressions stops as soon as the result is determined.
For && (logical AND), if the first operand evaluates to false (0), the entire expression is false, so the second operand is not evaluated.
For || (logical OR), if the first operand evaluates to true (non-zero), the entire expression is true, so the second operand is not evaluated.
For || (logical OR), if the first operand evaluates to true (non-zero), the entire expression is true, so the second operand is not evaluated.
Comparison Operators¶
C's comparison operators are numerical:
- Equal to
==
- Not equal to
!=
- Comparisons
<
,>
,<=
,>=
Remember from earlier material that in C, characters are also treated as numbers according to the ASCII encoding. So whenever a condition is numerically true, the code block of the statement will be executed.
In C, multiple conditions or comparisons cannot be combined into a single condition; instead, comparisons must be separated and combined with a logical operator. For example:
// Python: ('a' <= character <= 'z')
if ((character >= 'a') && (character <= 'z'))
{
...
}
Note! To avoid bugs, remember to use parentheses around different expressions in comparison and conditional structures to ensure that your intention is directly reflected in the code. Otherwise, you must be very familiar with the order of operations of C operators!
Note! Another common programming mistake is using the assignment operator
=
instead of the comparison operator ==
. Similarly, the bitwise operator &
can be confused with the logical AND operator &&
. The compiler will usually warn about these.Conditional Structures¶
There are two conditional structures in C. The traditional
if-else
structure and the multi-part conditional structure switch
.If-else if-else Discussion¶
C's conditional statements include the same
if
, else if
, and else
branches found in other programming languages, and they function the same way. The branches are tested in the given order, and the first one that evaluates to true will execute its associated code block, and the structure will exit. Conditional expressions are placed inside parentheses ( )
.if (number < 10) {
printf("The number is less than 10");
}
else if (number < 100) {
printf("The number is less than 100");
}
else if (number < 1000) {
printf("The number is less than 1000");
}
else {
printf("The number is equal to or greater than 1000");
}
Sometimes you might see C control structures implemented without code blocks. This is perfectly valid C code, in which case only the statement following the condition is executed if the condition is true.
if (number < 100)
printf("The number is less than 100");
Note! If you want to execute multiple statements when a condition is true, you must use a block, so it's good practice to always use curly braces from the very beginning!
Switch Flips the Switch¶
C also has a multi-part conditional structure called
switch
, which tests whether a variable's value matches one of the given integer constants. The reasoning is that switch
might be clearer when checking constant values compared to if-else
structures.The syntax of the
switch
statement is as follows. Note that, unlike other structures, this statement does not use block markers but instead relies on the break;
statement to indicate where a block ends.switch (variable) {
case CONSTANT1:
statement;
statement;
break;
case CONSTANT2:
statement;
statement;
break;
default:
statement;
statement;
break;
}
The
switch
structure is processed from top to bottom, comparing the variable's value to the constants. If a match is found, the corresponding code block is executed until a break
statement is encountered. After that, the structure exits. The default
block is optional and is executed only if none of the other conditions match.If the
break
is omitted, execution will fall through from one block to the next until a break
is encountered or the structure ends. This can be useful if you know what you're doing, as it allows you to handle multiple conditions with the same block.In fact, the
switch
statements could be implemented using an equivalent if-else
structure:switch(choice) { if (choice == 'p') {
case 'p': length();
length(); } else if (choice == 'm') {
break; mass();
case 'm': } else if (choice == 't') {
mass(); volume();
break; } else if (choice == 'l') {
case 't': temperature();
volume(); } else {
break; error();
case 'l': }
temperature();
break;
default:
error();
break;
}
Here, the evaluation of conditions flows downward in the same way as in the
if-else
structure. So, in a way, the switch
structure is unnecessary, but it is an easy-to-read way to implement constant value checks with many parallel options. Additionally, you'll undoubtedly encounter this structure in code written by others, so it's good to know what it's all about.Loop Structures¶
In C, we can of course perform both counted and conditional looping.
Counting loops¶
Counting looping in C is implemented using the
The syntax is as follows.
for
statement. The statement in C is slightly more demanding for the coder than in other languages. The syntax is as follows.
for (initialization statement; condition statement; increment statement) { statement; statement; }
Now, to help with the
for
loop, we need a loop variable that indicates the iteration in the condition statement.- In the initialization statement, we give the loop variable its starting value.
- In the condition statement, we test the value of the loop variable.
- If the condition is met, the code block is executed.
- If the condition is not met, the loop is exited.
- The increment statement defines how the loop variable changes for the next iteration, i.e., for the next condition test.
Examples.
uint8_t i; uint8_t i; uint8_t i;
for (i=0; i < 10; i++) { for (i=10; i > 0; i--) { for (i=0; i <= 20; i+=2) {
do_something(); do_something(); do_something();
} } }
In the first case, the value of the loop variable increases by one, in the second case it decreases by one, and in the third case it increases by two.
As can be seen, various comparisons can be made in the condition statement of the
for
loop. And the increment statement can take different forms... in fact, (almost) all C operators and even function calls whose return values are tested can be used there!And to make the
for
loop not too simple, we can initialize multiple loop variables within it, separated by commas. This allows us to conveniently have multiple auxiliary variables within the code block...int i,j;
for (i=0, j=10; i < 10; i++, j--) {
do_something(i,j);
}
Conditional Looping¶
Conditional looping in C can be implemented in two ways.
Test First, Then Execute¶
In the
while
statement, the code block is executed in the loop as long as the condition is true. Therefore, the while
loop is best suited for situations where the number of repetitions is not known in advance.The syntax is very straightforward.
while (condition) { statement; statement; }
A simple example. On the side, the equivalent
for
statement for the C compiler.uint8_t i=0; uint8_t i=0;
while (i < 10) { for (i=0; i < 10; i++) {
do_something(); do_something();
i++; }
}
Sometimes the
for
statement may be stylistically better because it keeps the loop control statements close to each other structurally.Execute First, Then Test¶
Again, the
do-while
structure executes its code block first and then tests the condition statement afterward. If the condition is false, the loop is exited. This structure can sometimes be a stylistically better solution than the while
structure, for example, if a repetitive operation on the variables in the condition needs to be performed before testing them.The syntax is not particularly special.
do { statement; statement; } while (condition);
Let's Take a Break¶
Related to control structures, we can also control the flow of code within a block using the commands
break
and continue
.Now, with the
break
command, we can jump out of any block even in the middle of its execution. For example:while (x > 0) {
if (x == SPECIAL_CONSTANT) {
break;
}
x = do_something();
}
Then
continue
jumps to the next iteration of the loop, but of course, it executes the increment statement first. For example, the function do_something
is not executed if the equality condition in the if statement is met.for (x=0; x < 100; x++) {
if (x == SPECIAL_CONSTANT) {
continue;
}
do_something();
}
Infinite Loop¶
One special case in the use of loop structures is very common in embedded systems programming.
Sometimes, the functionality of an embedded program is implemented inside a single infinite loop, which is part of the program's
main
function. As noted earlier, a C program runs until this function ends. So, conveniently (well, not really...), in an embedded program, waiting for input is done by running an infinite loop. This type of solution is called a superloop structure and might be familiar from Arduino programming, for example.int main() {
while (1) {
if (check_input()) {
do_something();
}
}
}
Input to the program can be obtained by polling, as in the example above with the
check_input
function, or by interrupts. Well, more on that in future material...Although this solution is simple, it is not technically good from a programming perspective. For example, because the condition testing is sequential and not parallel. Secondly, most of the execution time, the program tests statements that do not actually occur from the device's perspective. If the device has the resources to run an operating system, it is a better solution to let it decide when each functionality of the program is executed. The operating system often also manages the device's power consumption programmatically, which can be more difficult to achieve in a superloop structure.
We will return to this when learning to program our Hardware device.
A Word on Coding Style¶
Let's further justify why it's a good idea to use curly braces. The following code snippet is valid C, but not very readable. For example, now if the programmer succumbs to Python-style indentation, which
if
statement does the else
structure belong to?if (n > 0)
m = n + 10;
if (n > 10)
m = n;
else
n += 10;
From the example, we also notice how difficult it can be to read nested
if-else
structures. Sometimes a beginner programmer creates overly long if-else
structures (which on paper will max out the print quota). Later, finding a bug in such a program is a really thankless task and can actually take hours of time.In this course, we do not write such code, but we know how to divide our program into clear blocks and functions that are at most one screen in length.
In Conclusion¶
With the looping skills learned from Python, you can handle everything in this course, as long as you learn the syntax and special cases. New concepts, such as
do-while
loops and switch
structures, are useful at times.Different C language standards support slightly different C code regarding code blocks and control structures. For example, in the old C89 standard, variables could not be declared in the initialization structure of a
for
loop but had to be declared earlier in the code. However, in current standards, this is possible.for (int a = 0; a < 10; a++) {
}
Since in C, the appearance of code has little significance, we can write
for
loops, for example, like this. The functionality of the program could not really be easily seen from the code...Final greetings from the assistants: In this course, we save everyone's time and effort by writing our own code in a modular fashion using functions and code blocks that are clearly readable with proper indentation, parentheses, and curly braces.
Give feedback on this content
Comments about this material