What's in Chapter 6?
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
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();
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.
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
}
}
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
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){
}
#define TDRE 0x80 // Transmit Data Register Empty Bit
// Wait for buffer to be empty, output ASCII to serial port
void SCI_OutChar(char data){
}
// 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
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
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.
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.
No comments:
Post a Comment