Chapter 4

advertisement
CHAPTER 4
Flow of Control Constructs
The goto statement
Reference: K&R, Chapter 3 (3.8)


C has a goto statement
Its syntax is as follows
goto_statement
label


::=
::=
goto label ;
identifier
The label does not have to be declared, it simply must exist somewhere
within that same function
The label is simply an identifier
example
for (...)
for (...) {
...
if (disaster)
goto error;
}
...
error:
clean up the mess


This approach is useful if the error handling code is non-trivial, or if
errors can occur in many places
The alternative is to use flags to indicate whether we are in an error state
and to then exit the loop, thus requiring an explicit test, when such an
error state exists
Copyright (c) 1999 by Robert C. Carden IV, Ph.D.
2/6/2016
Control Flow
Labeled statements

More formally, statements may be preceded with a label which can either
be jumped to via a goto statement or technically within a switch
statement
labeled_statement
switch_statement






::=
|
|
::=
identifier : statement
case constant-expression : statement
default : statement
switch (expression) statement
The label names are said to have function scope, that is they must be
unique within a given function.
However, the same label may be used in different functions.
This makes sense considering that you cannot jump from one function to
another via a goto statement
Goto statements can and should be avoided
In general, by breaking things up into small enough functions, one can
return from them at any point and emulate a goto
If you find the need to use a goto, then you should probably consider
making the code from which you branched a separate function; then
return from it when you would have done a goto
4-2
Control Flow
The switch statement
Reference: Rojiani, Chapter 6 (6.11)
The following is a typical example of a switch statement:
switch (c) {
case 'a':
++a_cnt;
break;
case 'b':
++b_cnt;
break;
case 'c': /* FALLTHRU */
case 'C':
++c_cnt;
break;
case 'd':
++d_cnt; /* note duplication */
break;
case 'D':
++d_cnt;
break;
default:
++other_cnt;
break;
}







The controlling expression of the switch statement must be integer
valued. The usual automatic conversions are performed on the
controlling expression.
Each case is labeled by one or more integer-valued constant expressions.
Each case expression must be unique.
Default case is optional and is executed when no other cases are
satisfied.
Cases and default clause may appear in any order.
The break statement, when encountered, causes an immediate exit from
the switch.
Without the break statement, flow of control falls through to the next
case. This allows several case labels to have a single action or share a
particular action. Normally, each case must end with a break statement.
4-3
Control Flow
The switch statement (2) -- the effect of a switch
1. Evaluate the switch statement
2. Go to the case label having a constant value that matches the value of the
expression found in step 1, or if a match is not found, go to the default
label, or if there is no default label, terminate the switch.
3. Terminate the switch when a break statement is encountered, or
terminate the switch by "falling off the end."
example
void printit (int i)
{
switch (i) {
case 0:
printf ("I see a zero\n");
case 1:
printf ("That was either a 0 or 1.\n");
break;
case 2: /* FALLTHRU */
case 3:
printf ("Either a 2 or 3 -- %d\n", i);
break;
case 4:
printf ("I see a 4.\n");
break;
case -1:
printf ("A -1\n");
case -2:
printf ("Either a -1 or -2\n");
break;
default:
printf ("A %d -- interesting\n", i);
break;
/* not required */
}
}
4-4
Control Flow
Syntax of switch statement

The precise syntax for a switch statement is as follows
Switch_statement
Labeled_statement
::=
::=
|
|
statement
Compound-statement






switch (integer-expression) statement
identifier : statement
case constant-integer-expression : statement
default : statement
::=
|
|
|
|
|
|
::=
labeled-statement
expression ;
;
compound-statement
selection-statement
iteration-statement
jump-statement
{ {declarator}* {statement}* }
The switch statement is a multiway conditional statement
It generalizes upon the if-else construct
Informally, the syntax for a switch statement is as follows
switch_statement
::=
case_statement
case_part
default_statement
switch_block
::=
::=
::=
::=
case_group
default_group
::=
::=
switch (integer_valued_expression)
labeled_statement | switch_block
{case_part}+ statement
case constant_integer_expression :
default : statement
{ {declaration_list}? {case_group}*
{default_group}? {case_group}* }
{case_part}+ {statement}+
default : {statement}+
Names which are in italics represent nonterminals.
If nonterminals are enclosed in {braces}, then they are either followed by
a + (meaning one or more), a * (meaning zero or more) or a ? (meaning
zero or one, i.e. optional)
Keywords and terminals are in boldface
4-5
Control Flow
The switch statement versus else-if constructs





Many switch statements can be written as a series of if-else statements.
However, in many cases, the switch statement is actually more efficient
than the if-else.
In the following example, the if-else will perform each test in sequence
until it finds a match.
The switch statement, on the other hand, will evaluate c and then jump
directly to the desired statement.
Most C compilers create jump tables for the switch statement, thus
allowing the program to find the desired case instantaneously as opposed
to searching for it one by one.
switch solution
switch (c) {
case 'a': case 'A':
...
break;
case 'b': case 'B':
...
break;
case 'c': case 'C':
...
break;
default:
...
break;
}
equivalent if-else solution
if (c == 'a' || c == 'A'){
...
} else if (c == 'b' || c == 'B'){
...
} else if (c == 'c' || c == 'C'){
...
} else {
...
}
4-6
Control Flow
Bitwise operators
Reference: Kelley & Pohl, Chapter 7

C provides six operators for bit manipulation
 These may be applied only to integral operands (signed or
unsigned), e.g. char, short, int, or long.
 Operands should be unsigned for portability
Operator
&
|
^
<<
>>
~



Semantics
bitwise and
bitwise or
bitwise exclusive or
left shift
right shift
one's complement negation (unary)
An integer operand is treated as if it were a string of bits
Bitwise operators in C perform those operations on each corresponding
bit
These operations are illustrated in the truth table below
x
0
0
1
1
y
0
1
0
1
x & y
0
0
0
1
x | y
0
1
1
1
4-7
x ^ y
0
1
1
0
~ x
1
1
0
0
Control Flow
Bitwise operators (2) -- example
Compute the following:
0x3762 & 0xABCD
Treat each hex digit as a 4-digit binary number
Using this approach, each digit is an abbreviation for the following binary
0
1
2
3
=
=
=
=
0000
0001
0010
0011
4
5
6
7
=
=
=
=
0100
0101
0110
0111
8
9
A
B
=
=
=
=
1000
1001
1010
1011
C
D
E
F
Thus,
0x3762 = 0011 0111 0110 0010
0xABCD = 1010 1011 1100 1101
& ------------------------------0010 0011 0100 0000 = 0x2340
The bitwise operator acts on each individual corresponding bit
Similiary,
07142 | 05216
Treat each octal digit as a 3-digit binary number. Thus,
07142 = 111 001 100 010
05216 = 101 010 001 110
| -------------------------111 011 101 110 = 07356
4-8
=
=
=
=
1100
1101
1110
1111
Control Flow
Bitwise operators (3) -- example
n = n & 0177;




Unsigned integer constants are guaranteed to behave as if they have the
naive binary representation, i.e.
Constant 0177 contains 0...01111111
The example above uses the octal constant to mask off all but the 7 low
order bits
That is, n gets assigned an integer with its 7 low order bits and with all
other bits set to 0
0 & x = 0
1 & x = x




This is portable
The compiler is required to generate the correct code
The compiler has the job of handling case of how bytes are arranged
within a word
It is the compiler's job to support the C model and to generate whatever
machine code is necessary to make it work
4-9
Control Flow
Bitwise operators (4) -- example
x = x | 03; /* binary 011 */


In this example, the or '|' operator is used to turn the 2 low order bits on.
The other bits are left unchanged.
0 | x = x
1 | x = 1
x = 0x7F ^ 0xA;

In this example, the exclusive or '^' operator puts a 1 in each bit position
where its operands have different bits, and a 0 where they have the same
bits.
x
<-- 0111 1111 ^ 0000 1010
0111 0101
0x75
NOTE

Logical operators && and || are different from bitwise operators & and
|
x = 1; y = 2;
x & y
/* zero */
x && y
/* one */
4-10
Control Flow
Bitwise operators (5) -- example
x = x & ~077

In this example, the not '~' operator performs one's complement
negation.
 The effect of this statement is to set the 6 low-order bits of x to zero.
 This is a machine independent operation, i.e.:
~077 == 111...1000000
where the number of leading ones depends on the machine's word size.
example
On a 16 bit machine:
~1 == 0xfffe
On a 32 bit machine:
~1 == 0xfffffffe
Right and left shift


Places zeroes in vacated bits of positive (unsigned) constants
Remember them by the way they point:
>>
<<
expression
0x4U << 2
0x8U >> 2
right shift
left shift
binary value
0000 0100
0000 1000
Result
0x10
0x2
4-11
binary value
0001 0000
0000 0010
Control Flow
Bitwise operations (6) -- printing an int bitwise

The following function will use masks to print out an unsigned integer
argument bitwise.
/* print an unsigned int expression bitwise */
void print_bits (unsigned int v)
{
int i;
int N = 32; /* assume 32 bits */
unsigned int mask = 1 << (N - 1); /* 100...0 */
for (i = 0; i < N; i++)
{
if ((v & mask) == 0)
putchar ('0');
else
putchar ('1');
mask >>= 1; /* mask = mask >> 1 */
if ((i % 8) == 7 && i != (N - 1))
putchar (' ');
}
}





Because we are printing out all the bits in an unsigned integer, this
routine cannot be written without knowing how large an integer is on
that machine.
 Unfortunately, C has no operator to determine this
 It is possible, however, to write a function to do this
 Likewise, macros in <limits.h> can be used to determine this
Variable mask is initialized so that it has a 1 in the high order bit and
zeroes everywhere else, i.e. 000...001 << 31 yields the bit string
100...000
The loop prints out each digit with one at a time, starting with the most
significant digit
After printing 8 digits, it prints a space
It is very careful not to print a space after the very last digit
4-12
Control Flow
Bitwise operators (7) -- figuring how many bits may be shifted


In the previous exercise, we assumed that an unsigned integer had 32 bits
which could be printed
What we really want to be able to determine is how many bits we can get
at by shifting
1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include <stdio.h>
int main (void)
{
int i;
unsigned int x = 0x1;
for (i = 0; x != 0; i++)
x = x << 1; /* x <<= 1 */
printf ("%d bits in an unsigned int\n",
i);
return 0;
}
This approach was used to determine bit sizes on a 200 MHz
Pentium/MMX (Windows NT 4.0 SP3, MSVC 5.0) and the Unisys ASeries (mva15a)
Type
Char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
Bitsize on P200/NT
8
8
16
16
32
32
32
32
4-13
Bitsize on A-Series
8
8
39
39
39
39
39
39
Control Flow
Bitwise operators (8) -- example
Suppose we want to turn ON bit 15 in an unsigned long variable x
We do so by OR-ing in a 1 at the appropriate position
The integer 1 has a 1 at position 0, i.e. its bit pattern is
000...001
We need to shift this so that the 1 is in bit position 15, not 0
We can achieve this by shifting it to the left 15 positions, i.e.
1 << 15
Thus, to turn on bit 15 in x, we OR this expression, as in
x | 1 << 15
This expression is x with bit 15 turned on
We can modify x by incorporating an assignment
x |= 1 << 15;
4-14
Control Flow
Bitwise operators (9) -- example
Suppose we want to turn OFF bit 12 in an unsigned long variable x
We do so by AND-ing in a 0 at the appropriate position
All of the other bits in the mask must be 1
The integer 1 has a 1 at position 0, i.e. its bit pattern is
000...001
If we complement 1, as in ~1, then its bit pattern is
111...110
We need to first shift the 1 so that it is in bit position 12, not 0
We can achieve this by shifting it to the left 12 positions, i.e.
1 << 12
We then complement this pattern:
~(1 << 12)
Thus, to turn off bit 12 in x, we AND this expression, as in
x & ~(1 << 12)
This expression is x with bit 12 turned off
Naturally, we can also modify x itself, as follows:
x &= ~(1 << 12)
4-15
Control Flow
Bitwise operators (10) -- example
Suppose we wish to extract 8 bits from x starting at position 15
We notate this abstractly by writing x[15:8]
First we need to create a mask with 8 bits on and AND it with x
To create the mask, we complement 0 and shift it left 8 positions:
~0 << 8
Note that the 8 low-order vacated bits are filled with 0's.
We complement that again so that 8 vacated bits become 1's
~(~0 << 8)
We now shift the bits at position 15 right so that they are right justified
That us we want bit 15 in x to become bit 7
To do this we right shift x 8 bits:
x >> 8
Now we can AND these two expressions together:
~(~0 << 8) & (x >> 8)
4-16
Control Flow
Bitwise operators (11) -- example
 Write a function to return an n-bit field from its argument x starting
at position p, assuming bit 0 is rightmost.
 We notate this abstractly by writing x[p:n]
/**
** getbits: get n bits from position p */
**/
unsigned int
getbits (unsigned int x, unsigned int p, unsigned int n)
{
unsigned int one, mask, right_x;
/* first create a string of ones */
one = ~0;
/* put zeroes in the rightmost n bits, then negate to
make it all ones as the rightmost n digits */
mask = ~(one << n);
/* p denotes the left-most bit desired; shift
argument so that the n bits starting at position
p are rightmost */
right_x = x >> (p + 1 – n);
/* return result */
return right_x & mask;
}
n
x
k-1
p
where k = #bits on this machine
p+1-n
0
return
n-1
4-17
0
Control Flow
Bitwise operators (12) -- precedence


The following table lists the operators we have seen thus far
Reference: p. 53 of K&R
Operator
()
+ (unary) - (unary) !
~ ++ -- ( type )
*
/
%
+
<<
>>
<
<=
>
>=
==
!=
&
^
|
&&
||




Associativity
left to right
right to left
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
Notice that the left and right shift operators are higher in precedence
than the relational operators
On the other hand, the bitwise operators are lower in precedence than the
comparison operators
Also, notice that bitwise and, exclusive-or, and or are all different in
precedence.
The following example illustrates a potential bug one may encounter if
one does not pay attention to precedence.
if (v & mask == 0)
putchar ('0');
else
putchar ('1');

The problem is that == has higher precedence than & and so the test is
really v & (mask == 0) which is not what the user wanted.
4-18
Control Flow
Bitwise operators (13) -- two's complement





Many machines (and not the Unisys A-Series) use two's complement to
represent negative numbers.
A one's complement bit string is obtained by doing a bitwise negation on
the bit string, i.e. converting each corresponding 0 to 1 and each
corresponding 1 to 0 (as the ~ operator does)
A two's complement bit string is obtained by taking the one's
complement and than adding 1 to it
On a two's complement machine, the hardware that does an addition can
also be used to do subtraction. The operation a-b is the same as a+(-b)
where -b is obtained by taking the one's complement and then adding 1
The following table illustrates what various numbers would be on an 8
bit machine
Value
0
1
2
3
4




Binary
00000000
00000001
00000010
00000011
00000100
1's comp
11111111
11111110
11111101
11111100
11111011
2's comp
00000000
11111111
11111110
11111101
11111100
Value
0
-1
-2
-3
-4
Notice that on a two's complement machine, the two's complement of 0
is still all zeroes
The two's complement of 1 is all ones
Thus on a two's complement machine, 0 is all bits off and -1 is all bits on
Bitwise operations are portable only on unsigned numbers
Negative numbers -- avoid them when doing bitwise operations
 Doing bitwise operations on negative numbers can yield unpredictable
results
 A negative constant's binary representation is machine dependent
 Shift operations fill vacated bits with zeroes on unsigned quantities;
however, on signed quantities, right shifting may fill with sign bits
(arithmetic shift) or with 0-bits (logical shift) depending on the
architecture
 Avoid using negative constants in bit operations
 Thus, do not use signed variables for bitwise operations but use
unsigned variables instead
4-19
Control Flow
Precedence and order of evaluation - a complete table
Operator
() [] -> .
!
~ ++ -(unary) + (indirection) *
&
( type )
sizeof
*
/
%
+
<<
>>
<= < > >=
==
!=
&
^
|
Associativity
left to right
Order of Evaluation
-
right to left
-
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
&&
left to right
||
left to right
?:
right to left
left to right
sequence point after
first argument
short circuit
left to right
sequence point after
first argument
short circuit
first operand eval
sequence point after
first argument
=
+= -=
*= /= %=
&= |=
<<= >>=
,
right to left
-
left to right
left to right
sequence point after
first argument
4-20
Control Flow
The DO construct



The do statement can be considered to be a variant of the while
statement
It makes its test at the bottom of the loop instead of at the top
Its syntax is as follows
do_statement


::=
do statement while (expression);
The following table gives a do construct and its equivalent while
construct
Note that the body of the do construct is performed at least once
do construct
equivalent while construct
scanf ("%d", &i);
sum += i;
while (i > 0) {
scanf ("%d", &i);
sum += i;
}
Statement
while (expression)
statement
next statement
do {
scanf ("%d", &i);
sum += i;
} while (i > 0);
do
statement
while (expression);
next statement
Semantics of the DO construct
 The statement is executed
 Then the expression is evaluated
 If nonzero (true), the loop continues
 If zero (false), the loop terminates
 Sort of like a Pascal repeat-until construct, but not really
C
do <something>
while <expression> is true

Pascal
repeat <something>
until <expression> is true
This can confuse people, especially Pascal programmers
4-21
Control Flow
The DO construct (2) -- example
i = sum = 0;
do {
sum += i;
scanf ("%d", &i);
} while (i > 0);

This code fragment reads in digits until the user types in a negative or
zero digit

do/while look a lot like while statements -- style note
 Always enclose statements inside braces { } even if you only have
one statement
 That is, always make <statement> a compound statement so that the
do/while is not confused with the while construct
Thus, avoid code which looks like this:

void foo(int c)
{
/* Parse the current character and then read
* in some more and parse those too...
*/
do switch (c) {
case 'a':
...
break;
case 'b':
...
break;
case 'c':
...
break;
default:
...
break;
}
while ((c = getchar ()) != EOF);
}
4-22
Control Flow
break and continue

The following two statements have the a special effect within C
break;
exit from the innermost
enclosing loop or switch
statement


continue;
cause the current iteration of a
loop to stop and cause the next
iteration of the loop to begin
immediately
The continue statement is a bit tricky
The following table illustrates its effect on the various looping constructs
for
do
while
passes control to the increment step (third expression)
passes control to the bottom (test) part of the loop
passes control to the top (test) part of the loop
int c = 0;
while (c != EOF) {
c = getchar ();
if (c == 'y')
break;
if (c == 'n')
continue;
putchar (c);
}


The loop stops when it sees a 'y'
All characters besides an 'n' are printed
sum = 0;
for (i = 0; i < n; i++) {
if (i % 3 == 0)
continue;
if (i % 5 == 0)
continue;
sum += i;
}

This fragment adds up numbers not divisible by 3 or 5.
4-23
Control Flow
4-24
Download