Lecture 25 : Subroutines (Part 2)

advertisement
Subroutines (Part 2)
Programming Language Principles
Lecture 25
Prepared by
Manuel E. Bermúdez, Ph.D.
Associate Professor
University of Florida
Parameter Passing
• Formal parameters: Parameters that appear in the
declaration of a subroutine
• Actual parameters: expressions actually used in
procedure call.
• Some languages allow only ONE parameter passing
mode:
– C, Fortran, ML, Lisp.
• Other languages allow multiple parameter passing
modes:
– Pascal, Modula, Ada, Java
Main Parameter Passing Modes
1) Call by value: Pass the value of the argument.
– C, C++, Pascal (also allows pass by reference),
Java (primitive types).
– Can be expensive: large structures are copied.
2) Call by reference: Pass the address of the argument.
– Smalltalk, Lisp, ML, Clu, C++, Java (non
primitive types, including arrays).
– C, C++ have const mode: cannot change
parameter.
3) Call by name: Pass the text of the argument.
– C pre-processor (#define)
–
More later.
Choosing Parameter Passing Mode
• Many languages provide a choice:
– Pass by value:
• Intent is to not modify the argument.
– Pass by reference:
• Intent is to modify the argument.
• Often used to avoid expense of copying
large structure, even if there’s no intent to
modify it. Leaves door open for mistakes.
• In some languages (C, C++) modifying the
argument can be explicitly forbidden
(const)
Parameter Passing in Ada
ADA provides 3 modes: in, out, in-out.
• In: From caller to callee (call by value).
• Out: From callee to caller (call by result).
• In-out: Uses both. (call by value/result).
– Ada specifies that all three are to be
implemented by copying the values.
However, Ada specifically permits passing
either values or addresses.
Parameter Passing Modes: Example
Pass by value:
Output is (5,6,5)
var y:integer;
procedure
A(x:integer);
begin
write(x);
x := 1;
write(y+x);
end;
begin
y := 5;
A(y);
write(y);
end;
Parameter Passing Modes: Example
Pass by reference:
Output is (5,2,1)
var y:integer;
procedure
A(x:integer);
begin
write(x);
x := 1;
write(y+x);
end;
begin
y := 5;
A(y);
write(y);
end;
Parameter Passing Modes: Example
Pass by result:
Output is (??,6,1)
var y:integer;
procedure
A(x:integer);
begin
write(x);
x := 1;
write(y+x);
end;
begin
y := 5;
A(y);
write(y);
end;
Parameter Passing Modes: Example
Pass by value/result:
Output is (5,6,1)
var y:integer;
procedure
A(x:integer);
begin
write(x);
x := 1;
write(y+x);
end;
begin
y := 5;
A(y);
write(y);
end;
Parameter Passing in Ada (cont’d)
• Example:
• This Ada
program is
considered
erroneous: it
can tell the
difference
between pass
by reference
and
value/result.
Parameter Passing in Pascal
• Use keyword var
to effect pass by
reference.
• With var , output
is (5,2,1).
• Without var,
output is (5,6,5)
var y:integer;
procedure A(var x:integer);
begin
write(x);
x := 1;
write(y+x);
end;
begin
y := 5;
A(y);
write(y);
end;
Parameter Passing in C
• All parameters passed by value.
• However, can use pointers (which are
passed by value):
void swap (int *a, int *b) {
int t;
t = *a;
*a = *b;
*b = t;
}
...
swap(&p,&q);
Parameter Passing in C++
• C++ has reference parameters.
• Use the & prefix.
void swap (int &a, int &b) {
int t;
t = a;
a = b;
b = t;
}
swap (p,q);
• Parameters can be declared const.
References in C++
• Any variable can be a reference, allowing aliasing:
int
int
i =
j =
i;
&j = i;
2;
3;
• Aliasing is useful for function returns:
cout << a << b << c;
is short for
((cout.operator<<(a)).operator<<(b)).operator<<(c);
References in C++ (cont’d)
• Without references, << and >> would have to
return a pointer to their stream:
((cout.operator<<(a))->operator<<(b))->operator<<(c);
Or worse,
*(*(cout.operator<<(a)).operator<<(b)).operator<<(c);
• This would spoil the cascading syntax of operator
form:
*(*cout <<a) << b) << c;
Closures as Parameters
• Closure: Reference to a subroutine, including
the referencing environment.
• This allows higher order functions.
• In Pascal:
procedure apply_to_A
(function f(n:integer): integer;
var A:array [lo..hi:integer] of integer);
var i : integer;
begin
for i := lo to hi do A[i] := f(A[i]);
end;
Closures as Parameters (cont’d)
• Recall: referencing environments are required by
closures, only if subroutines can be nested.
• C, C++, Java get by with subroutine pointers
because there are no nested subroutines.
• In C,
void apply_to_A (int(*f)(int),
int A[],int A_size)
{
int i;
for (i=0; I < A_size; i++)
A[i]=f(A[i]);
}
Closures as Parameters (cont’d)
• Scheme:
(define apply-to-L (lambda (f l)
(if (null? l) '()
(cons (f (car l)) (apply-to-L f (cdr l))))))
• ML:
fun apply_to_L (f, l) =
case l of
nil => nil
| h :: t => f (h) :: apply_to_L(f, t);
Closures as Parameters (cont’d)
• RPAL (just for fun):
let apply_to_L f l =
helper f l (Order l)
where rec helper f l n =
n eq 0 -> nil
| (helper f l (n-1) aug f(l n))
Call by name
• A call-by-name parameter is evaluated in the
caller’s referencing environment when (and only
when) it is needed.
• Call by name equivalent to the "subst" function
used in lambda calculus, in normal order.
• Call by name allows strange expressions such as
infinite lists in Haskell:
take 10 [0..]
(take first 10 elements from infinite list)
Summation routine in Algol 60
Now,
y := sum(3*x*x-5*x+2, x, 1, 10);
will sum the values of 3x2 -5x+2, for 1 ≤ x ≤ 10.
Call-by-name in Algol 60
• Such uses of call-by-name (summation) are
rare.
• To implement call by name, Algol 60
implementations pass a hidden subroutine
that evaluates the actual parameter in the
caller's referencing environment. The hidden
routine is called a thunk.
• Calling thunks proved to be prohibitively
expensive.
• Call-by-name dropped in Algol 68.
Call by Name (cont’d)
Pass by name literally
substitutes n for a,
and m for b.
Result:
t := n;
n := m;
m := t;
swap is achieved.
procedure
swap(a,b:integer);
var t:integer:
begin
t := a:
a := b:
b := t;
end;
...
var n,m:integer;
n := 3;
m := 4;
swap(n,m);
Call by Name (cont’d)
However, attempting
to swap i and A[i]
results in
t := i;
i := A[i];
A[i] := t;
17 assigned to i, but
3 assigned to A[17]
(out of range)
swap not achieved.
procedure swap(a,b:integer);
var t:integer:
begin
t := a:
a := b:
b := t;
end;
...
var i:integer;
var A: array[1..10]
of integer;
i := 3;
A[3] := 17;
swap(i,A[i]);
Default Parameters
• Parameter initialized with a default value.
• If argument is not given, the default value is
used. (C++, Ada, Common Lisp,
Fortran 90)
• In C++, default parameters must be the last
ones:
void print (int value, int base=10)
{...};
print(31);
print(31,10);
print(31,16);
print(31,2); //output: 31 31 1f 11111
Default Parameters in Ada
type field is integer range 0..integer'last;
type number_base is integer range 2..16;
default_width : field := integer'width
default_base : number_base := 10;
procedure put (item : in integer;
width: in field := default_width;
base : in number_base := default_base)is ...
put(37)
prints 37 in decimal in 11 columns.
put(37,4) prints 37 in decimal in 4 columns.
put(37,4,8) prints 45 (37 in octal) in 4 columns.
Default Parameters in Ada (cont’d)
• We assumed that parameters are positional.
• Some languages allow parameters to be named.
• In Ada, (previous example):
put (item => 38, base => 8);
same as
put (base => 8, item => 38)
Variable number of arguments
• In C, C++:
#include <stdarg.h> /* macros and types defns */
int printf (char *format, ...) {
va_list args;
va_start (args, format);
part of code
char cp = va_arg(args,char);
double dp = va-arg(args,double);
}
Here we assume two arguments, of type char and double,
are expected. If not, chaos will ensue ...
Function Return Values
• Many languages restrict return types from
functions.
• Algol 60, Fortran: must be a scalar value.
• Pascal, Modula-2: must be scalar or pointer.
• Modula-3, Ada 95: allow a function to return
a subroutine (as a closure).
• C: function can return a pointer to a
function.
• Lisp, ML, Algol 68, RPAL: returning closures
is common.
Generics
• Often we need a single operation on a
variety of different object types.
– Queue a number.
– Push something on a stack.
– Existing subroutines for stacks of integers, but
now we need stacks of *trees* :-)
• Polymorphic subroutines (Lisp, RPAL)
provide a solution, but . . .
– No compile time checking.
– Compiler slower and more complicated.
– Forces structural view of type equivalence.
Generics (cont’d)
Other solution:
• Generic Modules -- Collection of similar subroutines or
modules that operate on different types.
• Useful to create containers:
– data abstraction that holds a collection of objects,
but
whose operations are generally oblivious to the
type of these objects. (Queue, heap, sorting)
• Generics (a.k.a. templates) appear in Modula-3, Clu,
Ada, C++, Java.
• Generic modules are a purely static mechanism. They
provide a mechanism to create needed source at
compile time.
Generic queue
package in Ada:
Queue contains up
to max_items
objects of type
item.
Operations are
enqueue and
dequeue.
Implemented as
circular list.
Generics must be
instantiated.
Generic
queues in
C++:
Generic
classes
must be
instantiated.
Generic
functions
need not be
instantiated.
Generic Sorting in C++
Generic functions in C++ need not be instantiated.
Example:
template<class T>
void sort(T A[], int A_size) { ... }
. . .
int ints[10];
double reals[50];
char *string[30];
. . .
sort(ints, 10);
sort(reals, 50);
sort(string, 30);
Generics in Ada (cont’d)
Ada doesn’t allow subroutines as parameters.
Can use generics instead:
Generics in Ada (cont’d)
• Now, we can instantiate:
subtype index is integer range 1 .. 100;
scores: array(index) of integer;
function foo (n: in integer)
return integer is
begin
... end;
procedure apply_to_ints is
new apply_to_array
(integer, int_array, foo);
apply_to_ints(scores);
Generics (cont’d)
Important:
Generics are strictly a compile-time issue.
Designers of Ada: “restricted form of contextsensititve macro facility”.
B. Stroustrup (designer of C++): “clever kind of
macro that obeys scope, naming, and type rules
of C++”.
Generics (cont’d)
Problem with generics:
• A certain type may permit an operation,
but the operation may not behave as
expected.
• Example: sorting algorithm. The <
behaves well for ints and doubles. For
characters and strings it will compare
ASCII values or pointers !
Exceptions
• An exception is an unexpected or unusual
condition that arises during program
execution. May be system-detected or
raised explicitly.
• Often I/O related, failure due to some I/O
related issue (end of file, wrong input,
etc.)
• Often necessary to repair stack (back up
execution), and proceed.
Exceptions (cont’d)
Before exceptions, to cope with errors:
1. Invent a value, used by caller, to detect
failure.
2. Return an explicit status value, examined
after every call.
3. Pass a closure (when available) for an errorhandling routine.
None of these are satisfactory (clutter, or hard to
program).
Exceptions (cont’d)
• Clu, Ada, Modula-3, C++, Java, ML all provide
exception handling:
• Programmer provides handlers that are
lexically bound to blocks of code.
• In general:
– If exception is not handled locally, control
propagates back up the dynamic chain until
either the program stops or a handler is
found.
Exceptions (cont’d)
• Exception handlers are (typically) attached to
a list of statements.
• Example (Java):
try{
int x = 5/n;
} //end try block
catch(ArithmeticException e){
System.out.println
("in catch, terminating method");
return;
} //end catch block
Exceptions (cont’d)
Exception handling used for 3 purposes:
1. Handler perform some magic recovery
(e.g. out of memory, free up garbage
memory.
2. If magic fails, at least inform the user
nicely (blue screen of death) 
3. Handler can clean up the mess (rollback
unfinished file writing, deallocate
memory, restore previous state, etc.)
Exceptions (cont’d)
• In Ada, exception is a built in-type:
declare empty_queue: exception;
• In Java/C++ an exception is (what else?)
an object:
class empty_queue { };
• In Module-3, an exception is another PL
"object":
EXCEPTION empty_queue;
Exceptions (cont’d)
• Explicitly raising exceptions:
throw statement (C++, Java, Lisp) .
raise statement (Ada, Modula-3, ML).
Exceptions (cont’d)
• Clu, Modula-3, C++, Java:
In each subroutine header, include a list of
exceptions that may propagate out of the
routine.
• Modula-3: List is mandatory: run-time error if
exception not listed, and exception not caught
locally.
• C++: List is optional: if present, same as
Modula-3; if not, all exceptions can propagate.
• Java: "checked" exceptions, must be declared;
"unchecked" exceptions do not.
Exception Syntax in C++
try {
... // protected code
} catch (end_of_file){
// library exception for eof
...
// handle eof error
} catch (io_error e) {
// any other I/O error
...
// handle io_error
} catch ( ... ){
// handle any other exception
// ... is an actual C++ token.
}
Exceptions in ML and Lisp
• In ML and common Lisp the exception handler is
attached to an expression:
val foo=(f(a)*b) handle Overflow => max_int;
• Overflow is a predefined exception. When
exception is raised, max_int will replace the
value of the expression.
Phrase-level Error Recovery in Recursive
Descent Parsing
At beginning of procedure A, check Next_Token:
If not acceptable, delete tokens until it is.
Exception Implementation
• To find a handler the system must "unwind" the
run-time stack, reclaiming stack frames.
• Obvious implementation is a linked-list stack of
handlers:
– When control enters a protected block, the
handler for that block is added to the list.
– Upon exception, the run-time system pops the
innermost handler off the list:
• If the exception matches, call the handler.
• If not, pop the list again.
Exception Implementation
The linked-list stack of handlers incurs run-time
overhead.
Other solution:
• Keep a table that maps exceptions to blocks of
code.
• When exception occurs, do binary search for
block of code, using PC as key.
• If exception is reraised, search again, using
return address as key.
Coroutines
• A coroutine is a closure into which we can transfer.
• Coroutines are execution contexts:
– Exist concurrently.
– Execute one at a time.
– Transfer control to each other explicitly.
• Transfer from one coroutine to another:
– Old program counter is saved.
– Update coroutine we are leaving.
– Different from a goto: after a goto, the PC is
forgotten.
• A transfer to a coroutine will take up where the
previous one left off.
Example of Coroutines
• Interleave execution of a
screen-saving routine, with
a file-system-check
routine.
Stack Allocation for Coroutines
• Coroutines are concurrent: can’t share a stack.
• Solution:
– Each coroutine has fixed amount of stack
space (Modula-2):
• Out of space: run-time error.
• Excess space is wasted.
– Stack frames allocated on the heap (Lisp,
Scheme):
• Increases overhead of subroutine call.
• Can allocate “chunks” of memory, big
enough for the frame.
Cactus Stacks
• If coroutines can be created at any nesting level,
(Simula), then we need a cactus stack.
Side-branch:
coroutine creation
(A, B, C, D).
Static links: arrows.
Dynamic links:
vertical stacking.
Subroutines (Part 2)
Programming Language Principles
Lecture 25
Prepared by
Manuel E. Bermúdez, Ph.D.
Associate Professor
University of Florida
Download