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