560 likes | 636 Views
Learn about different parameter passing modes in programming languages, including call by value, call by reference, and call by result. Explore examples in Ada, Pascal, C, and C++.
E N D
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 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; • Use keyword varto effect pass by reference. • With var , output is (5,2,1). • Without var, output is (5,6,5)
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 i; int &j = i; i = 2; j = 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); willsumthe 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) 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]); 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.
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); 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 ... part of code
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 context-sensititve 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: • Invent a value, used by caller, to detect failure. • Return an explicit status value, examined after every call. • Pass a closure (when available) for an error-handling 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: • Handler perform some magic recovery (e.g. out of memory, free up garbage memory. • If magic fails, at least inform the user nicely (blue screen of death) • 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.