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.

Simple Statements

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

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

is perfectly correct. Notice that each of the simple statements within the compound statement is terminated.

 

Compound Statements

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?... 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.

 

The If Statement

If statements provide a non-iterative choice between alternate paths based on specified conditions. They have either of two forms

or

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.

Figure 6.1: Example if statement.

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):}

Listing 6.2: A 3-wide median function.

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.

The Switch Statement

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

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

and the default prefix has the form

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

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
   }
}

Listing 6.3: Example of the switch statement.

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; }      

Listing 6.4: Example of the switch statement.

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

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

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

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;
}
    

Listing 6.5: Examples of the while statement.

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

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

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 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.,

A five-element array is set to zero, could be written as

or a little more efficiently as

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

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

The do statement is the third loop controlling statement in C. It is really just an execute-first while statement. It has the form

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.

The example of the five-element array could be written as

or as

or as

 

The Return Statement

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

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.

Null Statements

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.

The Goto Statement

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.

Missing Statements

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