Chapter 6: Flow of Control
What's in Chapter 6?
Simple statements
Compound statements
if and if-else statements
switch statements
while statements
for statements
do statements
return statements
goto statements
Null statements
Missing statements
Every procedural language provides statements for determining the flow of control within programs. Although declarations are a type of statement, in C the unqualified word statement usually refers to procedural statements rather than declarations. In this chapter we are concerned only with procedural statements.
In the C language, statements can be written only within the body of a function; more specifically, only within compound statements. The normal flow of control among statements is sequential, proceeding from one statement to the next. However, as we shall see, most of the statements in C are designed to alter this sequential flow so that algorithms of arbitrary complexity can be implemented. This is done with statements that control whether or not other statements execute and, if so, how many times. Furthermore, the ability to write compound statements permits the writing a sequence of statements wherever a single, possibly controlled, statement is allowed. These two features provide the necessary generality to implement any algorithm, and to do it in a structured way.
The C language uses semicolons as statement terminators. A semicolon follows every simple (non-compound) statement, even the last one in a sequence.
When one statement controls other statements, a terminator is applied only to the controlled statements. Thus we would write
if(x > 5) x = 0; else ++x;
with two semicolons, not three. Perhaps one good way to remember this is to think of statements that control other statements as "super" statements that "contain" ordinary (simple and compound) statements. Then remember that only simple statements are terminated. This implies, as stated above, that compound statements are not terminated with semicolons. Thus
while(x < 5) {func(); ++x;}
is perfectly correct. Notice that each of the simple statements within the compound statement is terminated.
The terms compound statement and block both refer to a collection of statements that are enclosed in braces to form a single unit. Compound statements have the form
{ObjectDeclaration?... Statement?... }
ObjectDeclaration?... is an optional set of local declarations.
If present, C requires that they precede the statements; in other
words, they must be written at the head of the block. Statement?...
is a series of zero or more simple or compound statements. Notice
that there is not a semicolon at the end of a block; the closing
brace suffices to delimit the end. In this example the local variable
temp
is only defined within the inner compound statement.
void main(void){ short n1,n2;
n1=1; n2=2;
{ short temp;
temp=n1; n1=n2; n2=temp; /* switch n1,n2 */
}
}
Listing 6.1: Examples of a compound statements
The power of compound statements derives from the fact that one may be placed anywhere the syntax calls for a statement. Thus any statement that controls other statements is able to control units of logic of any complexity.
When control passes into a compound statement, two things happen. First, space is reserved on the stack for the storage of local variables that are declared at the head of the block. Then the executable statements are processed.
One important limitation in C is that a block containing local declarations must be entered through its leading brace. This is because bypassing the head of a block effectively skips the logic that reserves space for local objects. Since the goto and switch statements (below) could violate this rule.
If statements provide a non-iterative choice between alternate paths based on specified conditions. They have either of two forms
if ( ExpressionList ) Statement1
or
if ( ExpressionList ) Statement1
else Statement2
ExpressionList is a list of one or more expressions and Statement is any simple or compound statement. First, ExpressionList is evaluated and tested. If more than one expression is given, they are evaluated from left to right and the right-most expression is tested. If the result is true (non-zero), then the Statement1 is executed and the Statement2 (if present) is skipped. If it is false (zero), then Statement1 is skipped and Statement2 (if present) is executed. In this first example, the function isGreater() is executed if G2 is larger than 100.
if(G2 > 100) isGreater();
A 3-wide median filter can be designed using if-else conditional statements.
short Median(short u1,short u2,short u3){ short result;
if(u1>u2)
if(u2>u3) result=u2; //
u1>u2,u2>u3 u1>u2>u3
else
if(u1>u3) result=u3; //
u1>u2,u3>u2,u1>u3 u1>u3>u2
else result=u1; //
u1>u2,u3>u2,u3>u1 u3>u1>u2
else
if(u3>u2) result=u2; //
u2>u1,u3>u2 u3>u2>u1
else
if(u1>u3) result=u1; //
u2>u1,u2>u3,u1>u3 u2>u1>u3
else result=u3; //
u2>u1,u2>u3,u3>u1 u2>u3>u1
return(result):}
For more information on the design and analysis of digital filters, see Chapter 15 of Embedded Microcomputer Systems: Real Time Interfacing by Jonathan W. Valvano.
Complex conditional testing can be implemented using the relational and Boolean operators described in the last chapter.
if ((G2==G1)||(G4>G3)) True(); else False();
Switch statements provide a non-iterative choice between any number of paths based on specified conditions. They compare an expression to a set of constant values. Selected statements are then executed depending on which value, if any, matches the expression. Switch statements have the form
switch ( ExpressionList ) { Statement?...}
where ExpressionList is a list of one or more expressions. Statement?... represents the statements to be selected for execution. They are selected by means of case and default prefixes--special labels that are used only within switch statements. These prefixes locate points to which control jumps depending on the value of ExpressionList. They are to the switch statement what ordinary labels are to the goto statement. They may occur only within the braces that delimit the body of a switch statement.
The case prefix has the form
case ConstantExpression :
and the default prefix has the form
default:
The terminating colons are required; they heighten the analogy to ordinary statement labels. Any expression involving only numeric and character constants and operators is valid in the case prefix.
After evaluating ExpressionList, a search is made for the first matching case prefix. Control then goes directly to that point and proceeds normally from there. Other case prefixes and the default prefix have no effect once a case has been selected; control flows through them just as though they were not even there. If no matching case is found, control goes to the default prefix, if there is one. In the absence of a default prefix, the entire compound statement is ignored and control resumes with whatever follows the switch statement. Only one default prefix may be used with each switch.
If it is not desirable to have control proceed from the selected prefix all the way to the end of the switch block, break statements may be used to exit the block. Break statements have the form
break;
Some examples may help clarify these ideas. Assume Port A is specified
as an output, and bits 3,2,1,0 are connected to a stepper motor.
The switch statement will first read Port A and the data with
0x0F (PORTA&0x0F)
. If the result is 5, then PortA is set to 6 and control is passed
to the end of the switch (because of the break). Similarly for
the other 3 possibilities
#define PORTA *(unsigned char volatile *)(0x0000)
void step(void){ /* turn stepper motor one step */
switch (PORTA&0x0F) {
case 0x05:
PORTA=0x06; // 6 follows 5;
break;
case 0x06:
PORTA=0x0A; // 10 follows 6;
break;
case 0x0A:
PORTA=0x09; // 9 follows 10;
break;
case 0x09:
PORTA=0x05; // 5 follows 9;
break;
default:
PORTA=0x05; // start at 5
}
}
For more information on stepper motors, see Chapter 8 of Embedded Microcomputer Systems: Real Time Interfacing by Jonathan W. Valvano.
This next example shows that the multiple tests can be performed for the same condition.
// ASCII to decimal digit conversion
unsigned char convert(unsigned char letter){ unsigned char digit;
switch (letter) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
digit=letter+10-'A';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
digit=letter+10-'a';
break;
default:
digit=letter-'0';
}
return digit; }
The body of the switch is not a normal compound statement since local declarations are not allowed in it or in subordinate blocks. This restriction enforces the C rule that a block containing declarations must be entered through its leading brace.
The while statement is one of three statements that determine the repeated execution of a controlled statement. This statement alone is sufficient for all loop control needs. The other two merely provide an improved syntax and an execute-first feature. While statements have the form
while ( ExpressionList ) Statement
where ExpressionList is a list of one or more expressions and Statement is an simple or compound statement. If more than one expression is given, the right-most expression yields the value to be tested. First, ExpressionList is evaluated. If it yields true (non-zero), then Statement is executed and ExpressionList is evaluated again. As long as it yields true, Statement executes repeatedly. When it yields false, Statement is skipped, and control continues with whatever follows.
In the example
i = 5;
while (i) array[--i] = 0;
elements 0 through 4 of array[ ] are set to zero. First i is set to 5. Then as long as it is not zero, the assignment statement is executed. With each execution i is decremented before being used as a subscript.
It is common to use the while statement ti implement gadfly loops
#define RDRF 0x20 // Receive Data Register Full Bit
// Wait for new serial port input, return ASCII code for key typed
char SCI_InChar(void){
while ((SCISR1 & RDRF) == 0){};
return(SCIDRL);
}
#define TDRE 0x80 // Transmit Data Register Empty Bit
// Wait for buffer to be empty, output ASCII to serial port
void SCI_OutChar(char data){
while ((SCISR1 & TDRE) == 0){};
SCIDRL = data;
}
For more information on serial ports, see Chapter 7 of Embedded Microcomputer Systems: Real Time Interfacing by Jonathan W. Valvano.
Continue and break statements are handy for use with the while statement (also helpful for the do and for loops). The continue statement has the form
continue;
It causes control to jump directly back to the top of the loop for the next evaluation of the controlling expression. If loop controlling statements are nested, then continue affects only the innermost surrounding statement. That is, the innermost loop statement containing the continue is the one that starts its next iteration.
The break statement (described earlier) may also be used to break out of loops. It causes control to pass on to whatever follows the loop controlling statement. If while (or any loop or switch) statements are nested, then break affects only the innermost statement containing the break. That is, it exits only one level of nesting.
The for statement also controls loops. It is really just an embellished while in which the three operations normally performed on loop-control variables (initialize, test, and modify) are brought together syntactically. It has the form
for ( ExpressionList? ;
ExpressionList? ;
ExpressionList? ) Statement
For statements are performed in the following steps:
The first ExpressionList is evaluated. This is done only once to initialize the control variable(s).
The second ExpressionList is evaluated to determine whether or not to perform Statement. If more than one expression is given, the right-most expression yields the value to be tested. If it yields false (zero), control passes on to whatever follows the for statement. But, if it yields true (non-zero), Statement executes.
The third ExpressionList is then evaluated to adjust the control variable(s) for the next pass, and the process goes back to step 2. E.g.,
for(J=100;J<1000;J++) { process();}
A five-element array is set to zero, could be written as
for (i = 4; i >= 0; --i) array[i] = 0;
or a little more efficiently as
for (i = 5; i; array[--i] = 0) ;
Any of the three expression lists may be omitted, but the semicolon separators must be kept. If the test expression is absent, the result is always true. Thus
for (;;) {...break;...}
will execute until the break is encountered.
As with the while statement, break and continue statements may be used with equivalent effects. A break statement makes control jump directly to whatever follows the for statement. And a continue skips whatever remains in the controlled block so that the third ExpressionList is evaluated, after which the second one is evaluated and tested. In other words, a continue has the same effect as transferring control directly to the end of the block controlled by the for.
The do statement is the third loop controlling statement in C. It is really just an execute-first while statement. It has the form
do Statement while ( ExpressionList ) ;
Statement is any simple or compound statement. The do statement executes in the following steps:
Statement is executed.
Then, ExpressionList is evaluated and tested. If more than one expression is given, the right most expression yields the value to be tested. If it yields true (non-zero), control goes back to step 1; otherwise, it goes on to whatever follows.
As with the while and for statements, break and continue statements may be used. In this case, a continue causes control to proceed directly down to the while part of the statement for another test of ExpressionList. A break makes control exit to whatever follows the do statement.
I=100; do { process(); I--;} while (I>0);
The example of the five-element array could be written as
i = 4;
do {array[i] = 0; --i;} while (i >= 0);
or as
i = 4;
do array[i--] = 0; while (i >= 0);
or as
i = 5;
do array[--i] = 0; while (i);
The return statement is used within a function to return control to the caller. Return statements are not always required since reaching the end of a function always implies a return. But they are required when it becomes necessary to return from interior points within a function or when a useful value is to be returned to the caller. Return statements have the form
return ExpressionList? ;
ExpressionList? is an optional list of expressions. If present, the last expression determines the value to be returned by the function. I f absent, the returned value is unpredictable.
The simplest C statement is the null statement. It has no text, just a semicolon terminator. As its name implies, it does exactly nothing. Why have a statement that serves no purpose? Well, as it turns out, statements that do nothing can serve a purpose. As we saw in Chapter 5, expressions in C can do work beyond that of simply yielding a value. In fact, in C programs, all of the work is accomplished by expressions; this includes assignments and calls to functions that invoke operating system services such as input/output operations. It follows that anything can be done at any point in the syntax that calls for an expression. Take, for example, the statement
while ((SCISR1 & TDRE) == 0); /* Wait for TDRE to be set */
in which the (SCISR1&TDRE)==0)
controls the execution of the null statement following. The null
statement is just one way in which the C language follows a philosophy
of attaching intuitive meanings to seemingly incomplete constructs.
The idea is to make the language as general as possible by having
the least number of disallowed constructs.
Goto statements break the sequential flow of execution by causing control to jump abruptly to designated points. They have the general form goto Name where Name is the name of a label which must appear in the same function. It must also be unique within the function.
short data[10];
void clear(void){ short n;
n=1;
loop: data[n]=0;
n++;
if(n==10) goto done;
goto loop;
done:
}
Listing 6.6: Examples of a goto statements
Notice that labels are terminated with a colon. This highlights the fact that they are not statements but statement prefixes which serve to label points in the logic as targets for goto statements. When control reaches a goto, it proceeds directly from there to the designated label. Both forward and backward references are allowed, but the range of the jump is limited to the body of the function containing the goto statement.
As we observed above, goto statements, cannot be used in functions which declare locals in blocks which are subordinate to the outermost block of the function.
Because they violate the structured programming criteria, goto statements should be used sparingly, if at all. Over reliance on them is a sure sign of sloppy thinking.
It may be surprising that nothing was said about input/output, program control, or memory management statements. The reason is that such statements do not exist in the C language proper.
In the interest of portability these services have been relegated to a set of standard functions in the run-time library. Since they depend so heavily on the run-time environment, removing them from the language eliminates a major source of compatibility problems. Each implementation of C has its own library of standard functions that perform these operations. Since different compilers have libraries that are pretty much functionally equivalent, programs have very few problems when they are compiled by different compilers.
Go to Chapter 7 on Pointers Return to Table of Contents