0% found this document useful (0 votes)
69 views46 pages

Chapter 6

Uploaded by

Rehan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
69 views46 pages

Chapter 6

Uploaded by

Rehan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 46

Chapter 6:: Control Flow

Programming Language Pragmatics


Michael L. Scott

Copyright © 2009 Elsevier


Control Flow

• Basic paradigms for control flow:


– Sequencing
– Selection
– Iteration
– Procedural Abstraction
– Recursion
– Concurrency
– Exception Handling and Speculation
– Nondeterminacy
Copyright © 2009 Elsevier
Chapter 6 focus:

• Basic paradigms for control flow:


– Sequencing: order of execution
– Selection (also alternation): generally in the
form of if or case statements
– Iteration: loops
– Recursion: expression is defined in terms of
(simpler versions of) itself
– Nondeterminacy: order or choice is deliberately
left unspecified
Copyright © 2009 Elsevier
Expression Evaluation

Precedence, associativity (see Figure 6.1 on


next slide)
– C has 15 levels - too many to remember
– Pascal has 3 levels - too few for good
semantics
– Fortran has 8
– Ada has 6
• Ada puts and & or at same level
– Lesson: when unsure, use parentheses!
Copyright © 2009 Elsevier
Expression Evaluation

Figure 6.1 Operator precedence levels in Fortran, Pascal, C, and Ada. The operator s at the top of the figure group most tightly.

Copyright © 2009 Elsevier


Infix, Postfix and Prefix
• Prefix: op a b or op(a,b)
– Example: standard in most we have seen
– In Lisp: (* (+ 1 3) 2)
• Infix: a op b
– Sometimes just “syntatic sugar”; for example, in C++,
a + b really calls operator+(a,b)
• Postfix: a b op
– Least common - used in Postscript, Forth, and
intermediate code of some compilers
– Also appears in C (and its descendants) and Pascal
examples, such as ++value in C and the pointer
dereferencing operator (^) in Pascal
Copyright © 2009 Elsevier
Expression Evaluation

• Ordering of operand evaluation (generally


none)
• Application of arithmetic identities
– Commutativity is assumed to be safe
– associativity (known to be dangerous)
a + (b + c) works if a~=maxint and b~=minint and c<0
(a + b) + c does not
• This type of operation can be useful, though, for
code optimization

Copyright © 2009 Elsevier


Expression Evaluation

• Short-circuiting
– Consider (a < b) && (b < c):
• If a >= b there is no point evaluating whether b < c
because (a < b) && (b < c) is automatically false
– Other similar situations
if (b != 0 && a/b == c) ...
if (*p && p->foo) ...
if (unlikely_condition &&
very_expensive function()) ...

• To be cautious - need to be sure that your second


half is valid, or else coder could miss a runtime
error without proper testing.
Copyright © 2009 Elsevier
Expression Evaluation
• Variables as values vs. variables as references
– value-oriented languages
• C, Pascal, Ada
– reference-oriented languages
• most functional languages (Lisp, Scheme, ML)
• Clu, Smalltalk
– Algol-68 is halfway in-between
– Java deliberately in-between
• built-in types are values
• user-defined types are objects - references
Copyright © 2009 Elsevier
Expression versus statements
• Most languages distinguish between expressions
and statements.
– Expressions always produce a value, and may or may
not have a side effect.
• Example: In python, b + c
– Statements are executed solely for their side effects,
and return no useful value
• Example: in Python, mylist.sort()

• A construct has a side effect if it influences


subsequent computation in some way (other than
simply returning a value).
Copyright © 2009 Elsevier
Expression Evaluation
• Expression-oriented vs. statement-oriented
languages
– expression-oriented:
• functional languages (Lisp, Scheme, ML)
• Algol-68
– statement-oriented:
• most imperative languages
– C halfway in-between (distinguishes)
• allows expression to appear instead of statement, but not the
reverse

Copyright © 2009 Elsevier


Algol 68
• Orthogonality
– Features that can be used in any combination
– Algol 68 is primary example. Here, everything is
an expression (and there is no separate notion of
statements).
• Example:
begin
a := if b<c then d else e;
a := begin f(b); g(c); end;
g(d);
2+3;
end
Copyright © 2009 Elsevier
Assignment shortcuts

• Assignment
– statement (or expression) executed for its side
effect - key to most programming languages
you have seen so far.
– assignment operators (+=, -=, etc)
• Handy shortcuts
• avoid redundant work (or need for optimization)
• perform side effects exactly once
– Example: A[index_fn(i) ]++;
– versus A[index_fn(i) ] = A[index_fn(i) ] + 1;
Copyright © 2009 Elsevier
Multiway Assignment

• Some languages (including ML, Perl,


Python and Ruby) allow multiway
assignment.
– Example: a,b = c,d;
– Defines a tuple; equivalent to a = c; b=d;
• Note that this can simplify things:
– a,b = b,a; (* no need for an aux variable)
– a,b,c = foo(d,e,f); (*allows a single return *)

Copyright © 2009 Elsevier


C and assignments within expressions
• Combining expressions with assignments can
have unfortunate side effects, depending on the
language.
– Pathological example: C has no true boolean type (just
uses ints or their equivalents), and allows assignments
within expressions.
– Example:
• if (a =b) {

}
What does this do?
Copyright © 2009 Elsevier
Side effects and functions

• Side Effects
– often discussed in the context of functions
– a side effect is some permanent state change
caused by execution of function
• some noticable effect of call other than return value
• in a more general sense, assignment statements
provide the ultimate example of side effects
– they change the value of a variable

Copyright © 2009 Elsevier


Expression Evaluation

• Side effects are a fundamental aspect of the


whole von Neumann model of
computation.
– What is the von Neumann architecture?
• In (pure) functional, logic, and dataflow
languages, there are no such changes
– These languages are called SINGLE-
ASSIGNMENT languages
– They are very, very different.
Copyright © 2009 Elsevier
Expression Evaluation

• Several languages outlaw side effects for


functions
– easier to prove things about programs
– closer to Mathematical intuition
– easier to optimize
– (often) easier to understand
• But side effects can be nice
– consider rand()

Copyright © 2009 Elsevier


More on side effects

• Side effects are a particular problem if they affect


state used in other parts of the expression in which a
function call appears
– Example: a - f(b) - c*d. OK?
– It's nice not to specify an order, because it makes it easier
to optimize
– Fortran says it's OK to have side effects
• they aren't allowed to change other parts of the expression
containing the function call
• Unfortunately, compilers can't check this completely, and most
don't at all
Copyright © 2009 Elsevier
Code optimization

• Most compilers attempt to optimize code:


– Example: a = b+c, then d = c + e + b
• This can really speed up code:
– a = b/c/d then e = f/d/c versus
– t = c*d and then a = b/t and e = f/t
• Arithmetic overflow can really become a problem
here.
– Can be dependent on implementation and local setup
– Checking provides more work for compiler, so slower
– With no checks, these can be hard to find
Copyright © 2009 Elsevier
Sequencing

• Sequencing
– specifies a linear ordering on statements
• one statement follows another
– very imperative, Von-Neuman
• In assembly, the only way to “jump” around is
to use branch statements.
• Early programming languages mimicked this,
such as Fortran (and even Basic and C).
Copyright © 2009 Elsevier
The end of goto

• In 1968, Edsger Dijkstra wrote an article


condemning the goto statement.
• While hotly debated after this, gotos have essentially
disappeared from modern programming language.
• This is the advent of “structured programming”, a
model which took off in the 1970’s. Emphasizes:
– Top down design
– Modularization of code
– Structured types
– Descriptive variables
– Iteration
Copyright © 2009 Elsevier
Alternatives to goto

• Getting rid of goto was actually fairly easy,


since it was usually used in certain ways.
– Goto to jump to end of current subroutine: use
return instead
– Goto to escape from the middle of a loop: use exit
or break
– Goto to repeat sections of code: loops

Copyright © 2009 Elsevier


Biggest need for goto

• Several settings are very useful for gotos,


however.
– Want to end a procedure/loop early (for example,
if target value is found).
• Solution: break or continue
– Problem: What about “bookkeeping”? We’re
breaking out of code which might end a scope -
need to call desctructors, deallocate variables, etc.
– Adds overhead to stack control - must be support
for “unwinding the stack”
Copyright © 2009 Elsevier
Biggest need for goto

• Another example: exceptions


• Goto was generally used as error handling, to
exit a section of code without continuing
• Modern languages generally throw and catch
exceptions, instead.
– Adds overhead
– But allows more gracefull recovery if a section of
code is unable to fulfull its contract.

Copyright © 2009 Elsevier


Sequencing
• Blocks of code are executed in a sequence.
• Block are generally indicated by { … } or similar construct.
• Interesting note: without side effects (as in Agol 68), blocks
are essentially useless - the value is just the last return
• In other languages, such as Euclid and Turing, functions
which return a value are not allowed to have a side effect at
all.
– Main advantage: these are idempotent - any function call will
have the same value, no matter when it occurs
• Clearly, that is not always desirable, of course. (Think of the
rand function, which should definitely not return the same
thing every time!)
Copyright © 2009 Elsevier
Selection

• Selection: introduced in Algol 60


– sequential if statements
if ... then ... else
if ... then ... elsif ... else
– Lisp variant:
(cond
(C1) (E1)
(C2) (E2)
...
(Cn) (En)
(T) (Et)
Copyright © 2009 Elsevier
)
Selection

• Selection
– Fortran computed gotos
– jump code
• for selection and logically-controlled loops
• no point in computing a Boolean value into a register, then
testing it
• instead of passing register containing Boolean out of
expression as a synthesized attribute, pass inherited
attributes INTO expression indicating where to jump to if
true, and where to jump to if false
Copyright © 2009 Elsevier
Selection

• Jump is especially useful in the presence of


short-circuiting
• Example (section 6.4.1 of book):

if ((A > B) and (C > D)) or (E <> F)


then
then_clause
else
else_clause

Copyright © 2009 Elsevier


Selection

• Code generated w/o short-circuiting (Pascal)


r1 := A -- load
r2 := B
r1 := r1 > r2
r2 := C
r3 := D
r2 := r2 > r3
r1 := r1 & r2
r2 := E
r3 := F
r2 := r2 $<>$ r3
r1 := r1 $|$ r2
if r1 = 0 goto L2
L1: then_clause -- label not actually used
goto L3
L2: else_clause
L3:

Copyright © 2009 Elsevier


Selection

• Code generated w/ short-circuiting (C)


r1 := A
r2 := B
if r1 <= r2 goto L4
r1 := C
r2 := D
if r1 > r2 goto L1
L4: r1 := E
r2 := F
if r1 = r2 goto L2
L1: then_clause
goto L3
L2: else_clause
L3:

Copyright © 2009 Elsevier


Selection: Case/switch
• The case/switch statement was introduced in Algol W to
simplify certain if-else situations.
• Useful when comparing the same integer to a large variety
of possibilities:
• i := (complex expression)
if i == 1: …
elsif i in 2,7: …
• Case (complex expression)
1: …
2-7: …

Copyright © 2009 Elsevier


Selection: Case/switch
• While it looks nicer, principle reason is code
optimization.
• Instead of complex branching, just loads possible
destinations into simple array.
• Additional implementations:
• If set of labels is large and sparse (e.g. 1, 2-7, 8-100,
101, 102-105, …) then can make it more space efficient
using hash tables or some other data structure.

Copyright © 2009 Elsevier


Iteration
• Ability to perform some set of operations
repeatedly.
– Loops
– Recursion
• Can think of iteration as the only way a function
won’t run in linear time.
• In a real sense, this is the most powerful
component of programming.
• In general, loops are more common in imperative
languages, while recursion is more common in
functional languages.
Copyright © 2009 Elsevier
Iteration
• Enumeration-controlled: originated in Fortran
– Pascal or Fortran-style for loops
do i = 1, 10, 2

enddo
– Changed to standard for loops later, eg Modula-2
FOR i := first TO last BY step DO

END

Copyright © 2009 Elsevier


Iteration: code generation
• At its heart, none of these initial loops allow anything
other than enumeration over a preset, fixed number of
values.
• This allows very efficient code generation:
R1 := first
R2 := step
R3 := step
L1: … --loop body, use R1 for I
R1 := R1 + R2
L2: if R1 <= R2 goto L1

Copyright © 2009 Elsevier


Iteration: code generation
• This can sometimes be optimized if the number of
iterations can be precomputed, although need to be careful
of overflow.
– Basically, precompute total count, and subtract 1 each time until
we hit 0.
– Often used in early Fortran compilers.
• Using iteration counts like this does require that we are
able to precompute!
• This type of prediction is always possible in Fortran or
Ada, but C (and its descendants) are quite different.

Copyright © 2009 Elsevier


Iteration: Some issues
• Can control enter or leave the loop other than through enumeration
mechanism?
– Usually not a big deal - break, continue, etc. (NOT goto.)
• What happens if the loop body alters variables used to compute end-
of-loop condition?
– Some languages only compute this once. (Not C.)
• What happens if the loop modifies the index variable itself?
– Most languages prohibit this entirely, although some leave it up to the
programmer.
• Can the program read the index after the loop has been completed, and
if so, what is its value?
– Ties into issue of scope, and is very language dependent.

Copyright © 2009 Elsevier


Iteration: Loops in C
• The for loop in C is called a combination loop - it allows one to use
more complex structures in the for loop.
• Essentially, for loops are almost another variant of while loops, with
more complex updates and true/false evaluations each time.
• Operator overloading (such as operator++) combined with iterators
actually allow highly non-enumerative for loops.
• Example:
for (list<int>::iterator it = mylist.begin(); it !=
mylist.end(); it++) {

}

Copyright © 2009 Elsevier


Iteration: iterator based loops
• Other languages (Ruby, Python, C# etc.) require any
container to provide an iterator that enumerates items in
that class.
• This is extremely high level, and relatively new.
• Example:
for item in mylist:
#code to look at items

Copyright © 2009 Elsevier


Iteration: logically controlled loops
• While loops are different than the standard, Fortran-style
for loops, since no set number of enumerations is
predefined.
• These are inherently strong - closer to if statements, in
some ways, but with repetition built in also.
• Down side: Much more difficult to code properly, and
more difficult to debug.
• Code optimization is also (in some sense) harder - none of
the for loop tricks will work.

Copyright © 2009 Elsevier


Recursion

• Recursion
– equally powerful to iteration
– mechanical transformations back and forth
– often more intuitive (sometimes less)
– naïve implementation less efficient
• no special syntax required
• fundamental to functional languages like Scheme

Copyright © 2009 Elsevier


Recursion: slower?
• Many criticize that recursion is slower and less
efficient than iteration, since you have to alter the
stack when calling a function.
• This is a bit inaccurate. Naively written iteration is
probably more effiecient than naively written
recursion.
• In particular, if the recursion is tail recursion, the
execution on the stack for the recursive call will
occupy the exact same spot as the previous method.

Copyright © 2009 Elsevier


Recursion

• Tail recursion
– No computation follows recursive call
int gcd (int a, int b) {
/* assume a, b > 0 */
if (a == b) return a;
else if (a > b) return gcd (a - b,b);
else return gcd (a, b – a);
}

– A good compiler will translate this to machine code


that runs “in place”, essentially returning to the start of
the function with new a,b values.
Copyright © 2009 Elsevier
Recursion: Continuations

• Even if not initially tail recursive, simple transformations


can often produce tail-recursive code.
• Known as continuation-passing. (more in a later chapter)
• Additionally, clever tricks - such as computing Fibonacci
numbers in an increasing fashion, rather than via two
recursive calls - can make recursion comparable.

Copyright © 2009 Elsevier


Order of evaluation

• Generally, we assume that arguments are evaluated before passing


to a subroutine, in applicative order evaluations.
• Not always the case: lazy evaluation or normal order evaluation.
pass unevaluated arguments to functions, and value is only
computed if and when it is necessary.
• Applicative order is preferable for clarity and efficiency, but
sometimes normal order can lead to faster code or code that won’t
give as many run-time errors.
• In particular, for list-type structures in functional languages, this
lazy evaluation can be key.

Copyright © 2009 Elsevier

You might also like