Upload
vuongdien
View
226
Download
7
Embed Size (px)
Citation preview
CHAPTER 4 Flow of Control Constructs
The goto statement
· C has a goto statement· Its syntax is as follows
goto_statement ::= goto label ;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
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 ::= identifier : statement| case constant-expression : statement| default : statement
switch_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
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.
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 */
}}
Syntax of switch statement
· The precise syntax for a switch statement is as follows
Switch_statement ::= switch (integer-expression) statementLabeled_statement ::= identifier : statement
| case constant-integer-expression : statement| default : statement
statement ::= labeled-statement| expression ;| ;| compound-statement| selection-statement| iteration-statement| jump-statement
Compound-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 ::= switch (integer_valued_expression)labeled_statement | switch_block
case_statement ::= {case_part}+ statementcase_part ::= case constant_integer_expression :
default_statement ::= default : statementswitch_block ::= { {declaration_list}? {case_group}*
{default_group}? {case_group}* }case_group ::= {case_part}+ {statement}+
default_group ::= 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
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 equivalent if-else solutionswitch (c) {case 'a': case 'A':
...break;
case 'b': case 'B':...break;
case 'c': case 'C':...break;
default:...break;
}
if (c == 'a' || c == 'A'){...
} else if (c == 'b' || c == 'B'){...
} else if (c == 'c' || c == 'C'){...
} else {...
}
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 y x & y x | y x ^ y ~ x0 0 0 0 0 10 1 0 1 1 11 0 0 1 1 01 1 1 1 0 0
Bitwise operators (2) -- example
Compute the following:
0x3762 & 0xABCD
Treat each hex digit as a 4-digit binary numberUsing this approach, each digit is an abbreviation for the following binary
0 = 0000 4 = 0100 8 = 1000 C = 11001 = 0001 5 = 0101 9 = 1001 D = 11012 = 0010 6 = 0110 A = 1010 E = 11103 = 0011 7 = 0111 B = 1011 F = 1111
Thus,
0x3762 = 0011 0111 0110 00100xABCD = 1010 1011 1100 1101
& ------------------------------- 0010 0011 0100 0000 = 0x2340
The bitwise operator acts on each individual corresponding bitSimiliary,
07142 | 05216
Treat each octal digit as a 3-digit binary number. Thus,
07142 = 111 001 100 01005216 = 101 010 001 110
| -------------------------- 111 011 101 110 = 07356
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 = 01 & 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
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 = x1 | 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 */
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...1000000where 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:
>> right shift<< left shift
expression binary value Result binary value0x4U << 2 0000 0100 0x10 0001 00000x8U >> 2 0000 1000 0x2 0000 0010
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');
elseputchar ('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 andzeroes 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
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
1234567891011121314
#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 A-Series (mva15a)
Type Bitsize on P200/NT Bitsize on A-SeriesChar 8 8
unsigned char 8 8short 16 39
unsigned short 16 39int 32 39
unsigned int 32 39long 32 39
unsigned long 32 39
Bitwise operators (8) -- example
Suppose we want to turn ON bit 15 in an unsigned long variable xWe do so by OR-ing in a 1 at the appropriate positionThe 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 0We 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 onWe can modify x by incorporating an assignment
x |= 1 << 15;
Bitwise operators (9) -- example
Suppose we want to turn OFF bit 12 in an unsigned long variable xWe do so by AND-ing in a 0 at the appropriate positionAll of the other bits in the mask must be 1The 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 0We 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)
Bitwise operators (10) -- example
Suppose we wish to extract 8 bits from x starting at position 15We notate this abstractly by writing x[15:8]First we need to create a mask with 8 bits on and AND it with xTo 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 justifiedThat us we want bit 15 in x to become bit 7To do this we right shift x 8 bits:
x >> 8
Now we can AND these two expressions together:
~(~0 << 8) & (x >> 8)
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 intgetbits (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; shiftargument so that the n bits starting at positionp are rightmost */
right_x = x >> (p + 1 – n);
/* return result */return right_x & mask;
}
Bitwise operators (12) -- precedence
· The following table lists the operators we have seen thus far· Reference: p. 53 of K&R
Operator Associativity() left to right
+ (unary) - (unary) !~ ++ -- ( type )
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');
elseputchar ('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.
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 Binary 1's comp 2's comp Value0 00000000 11111111 00000000 01 00000001 11111110 11111111 -12 00000010 11111101 11111110 -23 00000011 11111100 11111101 -34 00000100 11111011 11111100 -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
Precedence and order of evaluation - a complete table
Operator Associativity Order of Evaluation() [] -> . left to right -! ~ ++ --
(unary) + -(indirection) *& ( type )
sizeof
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 rightleft to right
sequence point after first argumentshort circuit
|| left to rightleft to right
sequence point after first argumentshort circuit
?: right to left first operand evalsequence point after
first argument=
+= -=*= /= %=
&= |= <<= >>=
right to left -
, left to rightleft to right
sequence point after first argument
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
do {scanf ("%d", &i);sum += i;
} while (i > 0);
scanf ("%d", &i);sum += i;while (i > 0) {
scanf ("%d", &i);sum += i;
}do
statementwhile (expression);next statement
Statementwhile (expression)
statementnext 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 Pascaldo <something>while <expression> is true
repeat <something>until <expression> is true
· This can confuse people, especially Pascal programmers
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);
}
break and continue
· The following two statements have the a special effect within C
break; continue;exit from the innermost enclosing loop or switch statement
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 passes control to the increment step (third expression)do passes control to the bottom (test) part of the loop
while 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;}