CMPS 350 Ch.8 Lecture Notes

Control Statements in Imperative Languages

resources:
obfuscated C contest
ANSI C docs
hw08.c
A control statement modifies the default line-by-line order of execution in a program by branching.
Types of control in a program:
  • precedence and associativity for expressions
  • forks, semaphores and threads for processes
  • resolution for logic languages
  • the function call for functional languages
  • subprograms for procedural languages
  • control statements for imperative languages (focus of this lecture)

    Control statements in imperative languages mirror the instruction-set hardware

  • no unconditional or subroutine jump in IBM 701 - if/else and while only
  • FORTRAN I control statements and LISP car/cdr based on IBM 704 hardware
  • RISC instruction set has two control instructions: conditional branch - branch on equal or not equal unconditional jump - to address or to label (MIPS architecture)
    UNCONDITIONAL BRANCH STATEMENTS

    Examples: goto, break, continue, redo
  • All unconditional branches are restricted and camouflaged goto's
  • Perl: last (jump out) next (jump to top) redo (to top w/o re-evaluation)
  • Java has no goto but has a labeled break and continue

     // unconditional branches in C:  (see code)
           j = 0;
           while (j < 3) {
             if (array[j] < 0) 
                 goto out_of_bounds;
             if (array[j] == 99) 
                 break;        // breaks to first line after loop
             if (array[j] > 1) {
                 j++;
                 continue;   // takes you to the top of the loop
             }
           }
           printf("break got me here\n");
           cleanup();
     out_of_bounds: printf("value %d got me here\n",array[j]); 
         for (i = 0; i < SIZE; i++)
         {
            if (num[i] <= 1.00)  
               continue;         //  skip values <= 1.00  
            printf("value = %.2f\n", num[i]);
         }
    
         for (i = 0; i < SIZE; i++)
         {
            if (num[i] <= 1.00)  
               break;   // exit loop completely for values <= 1.00 
            printf("value = %.2f\n", num[i]);
         }  

    THE INFAMOUS GOTO

    A heated debate in 1960's and 1970's; readability major concern
    
       ! Example of spaghetti code in Fortran
          GOTO 10   ! jump forward
       23 CONTINUE
          i = i - 1
          IF (i .eq. 0) GOTO 99
       10 PRINT "I Loop"
       69 j = j - 1 ! loop
          print "J Loop" 
          IF (j .ne. 0) GOTO 69
          GOTO 23   ! jump back
      099 CONTINUE
      
        // the same code without gotos is cleaner
        do {
            print "I Loop"
            while (j>0) {
               print "J Loop"
               j--
            }
            i--
        }
        while (i > 0)
       
    GOTOs can be messy but might be the best and cleanest way to
    1) get out of loops (see code)
    2) completely control complicated statement execution within the body of the loop; and
    3) eliminate non-tail recursion.

     // try converting this into a loop without GOTOs or unconditional jumps
         L1: if (cond1) then goto L2
               statement1;
             if (cond2) then goto L2
               statement2;
             goto L1;
         L2: statement3; 

    If the last thing a function does before the return is call itself, the function is tail recursive.

    
         // tail recursive - all the work is done on the way *up* the stack
        int sum = 0;
        void foo (int i, int list[]) {
          if (i == size)
              return;
          sum += list[i++];
          foo (i, list);  // last thing done is the recursive call
        } 
    
         // NOT tail recursive - all the work is done on the way *down* while
         // popping the call frames
         int fac (int n) {
           if (n == 0) return 1
           else
              return n * fac(n-1);  // last operation is mult 
         }
      
    If a function is tail recursive, you can easily convert the recursive call into a loop.

       // tail recursive function 
       void stuff (int n) { 
           print n; 
           if (n>0) 
               stuff (n-1); 
        }
    
        // converted into an equivalent loop 
        while (n > 0) { 
            print n; 
            n--;
        }

    Knuth, "Structured Programming with Goto Statements," Computing Surveys 6:4 (1974).
    Knuth shows that goto is the most intuitive solution to replace non-tail recursion:

     void treeprint(Node * t){
             if (t != null) {
                treeprint(t->left);
                print(t);
                treeprint(t->right);
             }
           }

    First replace the tail recursive call with a goto:

          void treeprint(Node * t){
          L:  if (t != null) {
                treeprint(t->left);
                print(t);
                t = t->right;  <= normally done in the recursive call
                goto L;
              }
          }
      

    The remaining recursive call can then be eliminated with a stack:

          void treeprint(Node * t){
              Stack s;  <= holds pointers 
          L1: while ( t != null ) {
                 s.push(t); 
                 p = t->left;
                 goto L1;     
          L2:    s.pop(t);
                 print(t->value);
                 t = t->right; 
              } // end while   
      
              if (!s.empty())
                 goto L2;
          }
      Example 6c.
      

    "The goto's can be eliminated without difficulty, but in general when a recursive call is embedded in several complex levels of control, there is no equally simple way to remove recursion without resorting to something like Example 6c" (p.282).

    CONDITIONAL BRANCH STATEMENTS

    Conditional branch control: selection statements, iterative (looping) statements and guarded commands.

    SELECTION STATEMENTS

    A selection statement is a choice between one or more paths of execution. Types:
  • 1-way (if)
  • 2-way (if..else)
  • n-way (if..elseif..else or switch)

    All selection statements have a condition:

  • if (boolean_expr) .... // condition returns T/F
  • if (arith_expr) .... // False if 0 - True otherwise
  • if (condition) stmnt // stmnt executed when condition is true - positive logic
  • if (!condition) stmnt // stmnt executed when condition is false - negative logic
             if (!EOF)            
                 read(ch);  // executed when EOF is false
    
    One-Way Selection Statements
  • if (condition) statement(s);
  • In 1-way selection you either take a path or you don't
  • 1-way selection and 'goto' can simulate everything (early Fortran)
  • Modern languages have n-way for writeability and readability

    Two-Way Selection Statements
        if (condition)
              statement(s);
           else
              statement(s); 
    The dangling else problem:
     
        if (sum == 0)
        if (count == 0)
        result = 0;
        else result = 1;   // which 'if' gets the 'else'?  
    Java/C/C++/C# static semantics rule is: else matches nearest if

    In Perl all then/else clauses are compound (solves dangling else)

    To force alternative semantics use compound statements:

       if (sum == 0) {
         if (count == 0)
            result = 0;
       }
       else result = 1;

    Multiple-Way Selection Statements

  • keyword: switch case select
  • what is control type (integral, float)?
  • how is fall through handled? (execute code in remaining cases?)
  • how is fall out handled? (implicit exit after hitting a case?)
  • is default value supported or required?

    C#'s SWITCH
    
     switch (k) { // must be integral type
        case 2:
           Console.Write("\nm is 1 or 2\n", k);
           j = 2*k-1;
           break;  //explicit break is required - no fall through
        case 5:
           Console.Write("\nm is 3 or 5\n", k);
           j = 3*k+1;
           break;
        case 4:
           Console.Write("\nm is 4\n", k);
           j = 4*k-1;
           break;
       case 1: 
           // no fall through allowed in C#
           break;
        default: // supported but not required
          Console.Write ("\neverything else, m =%d\n", k);
          j = k-2;
          break;
       }
    
     
         // syntax of C's switch statement
         switch (expression) {
            case const_expr_1: stmt_1;   
              ...
            case const_expr_n: stmt_n;
            [default: stmt_n+1]
         } 
    In C's switch statement,

  • control expression must be integral type
  • selectable code can be statement sequences, blocks, or compound statements
  • no implicit branch at end of selectable segments means fall through is allowed
  • optional 'default' is for unrepresented values
  • C see code

    Ada's "No Fall Through" is more reliable than C - once stmt_seq is completed, control is passed out

       case expression is
          when choice list => stmt_sequence;
          ...
          when choice list => stmt_sequence;
          when others => stmt_sequence;]
       end case;
       next_stmt; // control passed here  

    Multiple-Way Selection with elsif

    Unlike C, some languages include an explicit keyword for "else if" such as elsif or elif.

    In Python:
       if x < 0:
            x = 0
            print 'Negative changed to zero'
       elif x == 0:
            print 'Zero'
       elif x == 1:
            print 'Single'
      
    In Ada:
          if ...
             then ...
          elsif ...
             then ...
          elsif ...
             then ...
          else ...
          end if
      
    In Perl:
        if (num < 1.0) {
          print "1";        
        }
        elsif ( num < 2.0) { 
           print "2";
        }
        elsif ( num < 3.0) { 
           print "3";
        }
      

    ITERATIVE STATEMENTS

    The repeated execution of a statement or compound statement is accomplished by iteration or recursion
  • multiple entry points in a loop is controversial
  • multiple exit points in a loop is accepted practice

    General design issues for iteration control statements: #1 How is iteration controlled?
    #2 Where is the control mechanism in the loop?

    Counter-Controlled Loops

    A counting iterative statement has a loop variable and 3 parameters: initial value, final value, stepsize value

    Design Issues:

  • What are the type and scope of the loop variable?
  • What is the value of the loop variable at loop termination?
  • Can loop variable or parameters change in the loop body, and if so, how does this affect loop control?
  • Should loop parameters be evaluated only once, or once for every iteration?

    Example, FORTRAN 90:

     DO label var = start, finish [, stepsize]
      
        1. Loop variable must be INTEGER type
        2. Loop variable always has a last value
        3. The loop variable cannot be changed in the loop 
        4. Loop parameters are evaluated only once and can be changed 
           after evaluation w/o affecting loop execution 
        5. Stepwize can be any value but zero
        6. Parameters can be expressions
    
    Example, Pascal:
     for variable := initial (to|downto) final do statement
      
        1. Loop variable must be an ordinal type of usual scope
        2. After normal termination, loop variable is undefined
        3. The loop variable cannot be changed in the loop
        4. The loop parameters can be changed, but they are evaluated just once, so 
           it does not affect loop control 
    Example, Ada:
     for var in [reverse] discrete_range loop  
            ...
       end loop
    
        1. A discrete range is a sub-range of an integer or enumeration type
        2. Scope of the loop variable is the range of the loop
        3. Loop variable is implicitly undeclared after loop termination
    
    Example: C's for loop is LISP-like
        for ([expr_1] ; [expr_2] ; [expr_3]) statement
      
         which is equivalent to:
      
         expr_1;
         while ( expr_2 ) {
            statement
            expr_3;
         } 
    In C,
  • The expressions can be a single statement or a sequence of statements
  • The value of a statement sequence is the value of the last statement
  • There is no explicit loop variable
  • Everything can be changed in the loop
  • Expr_1 is evaluated once, the other two are evaluated with each iteration
  • See C for loop code examples

    C++ differs from C in two ways:
  • control expression can also be Boolean (C++ has a Boolean type)
  • Expr_1 can include variable definitions (scope is within loop)

    Java and C# force the exclusive use of Boolean expressions in control expressions.

     upside is security:
           while (i = 1)   // prevents this error since 'i' is INT type 
    
      downside is a lack of flexibility: 
           int num = 5;   // you want num of type int to control while 
           while (num){   // exit loop when num is 0
               x / num;      // a divide by zero error will never occur 
               num--;
           }
    

    Logically-Controlled Loops

    Repetition control based on a Boolean or arithmetic control expression. General forms:
       while (ctrl_expr)    // pre-test
       do loop body
      
       do loop body
       while (ctrl_expr)   // post-test
      
      
    Examples:
  • Pascal has separate pre-test and post-test logical loop statements (while-do and repeat-until)
  • C and C++ also have both - the control expression may be arithmetic (while-do and do- while)
  • In Java control expression must be Boolean and the body can only be entered at the top since Java has no goto
  • Ada has a pretest version, but no post-test
  • FORTRAN 77 and 90 have neither
  • Perl has pre-test while and until, but no post-test logical loop

    User-Located Loop Control Mechanisms

    (see code)
  • allows programmers to pick an exit other than top or bottom loop
  • Simple design for single loops (e.g., break)
  • Design issues for nested loops
  • Should the conditional be part of the exit?
  • Should control be transferable out of more than one loop?

    Iteration Based on Data Structures

  • 'foreach' is typical keyword
  • Perl, JavaScript, PHP, Java, and C#
  • Number of elements of in a data structure control loop iteration
  • Control mechanism is a call to an iterator that returns the next element, if no element, terminate loop
  • The order in which elements are returned is predefined
  • C's 'for loop' can be used to build a user-defined iterator:
     for (p=root; p==NULL; traverse(p))
      { 
        . . .
      }
     
  • Perl and C#'s 'foreach' iterates on the elements of arrays, hashes, files:
      
    # Perl foreach(@words) { $word = $_; $word =~ tr/A-Z/a-z/; print 'word: ', $_, "\n"; }
      
    // C# Strings[ ] = strList = {.Bob., .Carol., .Ted.}; foreach (Strings name in strList) Console.WriteLine (.Name: {0}., name);
    Perl's while (<FILEHANDLE>) iterates on the lines of a file.
    open(WORDS, $filename); while(<WORDS>) { . . . } close(WORDS);

    DIJKSTRA'S GUARDED COMMANDS

    A probabilistic choice construct that makes it possible to express randomised algorithms. Purpose:
  • To support a program verification during development
  • For concurrent programming in CSP and Ada
    Basic Idea: If the order of evaluation is not important, the program should not specify one
    SELECTION Guarded Command:
    if <Boolean exp> -> <statement>        do this
      [ ] <Boolean exp> -> <statement>    OR do this
       ...
      [ ] <Boolean exp> -> <statement>    OR do this
      fi
    Semantics:
  • When construct is reached, evaluate all Boolean expressions
  • If more than one are true, choose one non-deterministically
  • If none are true, it is a runtime error

    LOOP Guarded Command:

      do <Boolean> -> <statement>
      [ ] <Boolean> -> <statement>
       ...
      [ ] <Boolean> -> <statement>
      od
    
    

  • Semantics: for each iteration
  • Evaluate all Boolean expressions
  • If multiple are true, choose one non-deterministically; start loop again
  • If none are true, exit loop

    Example:
          if a >= b then print "More or equal";
          else print "Less";
      
      Guarded:
          if a >= b => print "More or equal"
          [ ] a < b => print "Less"
          fi
    
    
    Another example:
          if
              a >= b -> print "More or equal"
              a <= b -> print "Less or equal"
          fi
    

    When a = b, the result of command can be "More or equal" or "Less or equal".

    Use in Concurrent Programming

    Guarded commands are a means for controlling asynchronous message passing - allows all requestors an equal but nondeterministic chance of communicating ( see Ada code )

    Ada has concurrency built-in. A task is the basic unit of concurrency. Concurrent hello world

         with Ada.Text_IO; use Ada.Text_IO;
         with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random;
     
         procedure Concurrent_Hello is
             task type Writer (Message : access String);   
             task body Writer is
                Seed : Generator;
             begin
                Reset (Seed); 
                delay Duration (Random (Seed));
                Put_Line (Message.all);
             end Writer;
     
             S1 : aliased String := "Enjoy";
             S2 : aliased String := "Rosetta";
             S3 : aliased String := "Code";
             T1 : Writer (S1'Access);
             T2 : Writer (S2'Access);
             T3 : Writer (S3'Access);
        begin
           null;
        end Concurrent_Hello;
    

    IBM's latest OO/concurrent programming language for high-performance computing (e.g. Watson) X10

    TOP