Subprograms, Design Issues, Local Referencing, Parameter Passing, Overloaded Methods, Generic Methods, Design Issues for functions, Semantics of call and return, implementing simple subprograms, stack and dynamic local variables, nested subprograms, blocks and dynamic scoping,
1. Prepared by B.R.S.S. RAJU Page 1
UNIT-II
SUBPROGRAMS AND IMPLENTATIONS
Introduction to Subprogram
The first programmable computer, Babbage’s Analytical Engine, built in the 1840s, had the
capability of reusing collections of instruction cards at several different places in a program. In a
modern programming language, such a collection of statements is written as a subprogram.
This reuse results in several different kinds of savings, primarily memory space and coding time.
Such reuse is also an abstraction, for the details of the subprogram’s computation are replaced in a
program by a statement that calls the subprogram.
Advantages of using functions:
Easy programming code maintained
To share or to transfer the data more than one function
To increase reusability and to decrease program complexity
What are formal parameters? What are actual parameters?
The parameters in the subprogram header are called formal parameters. Subprogram call
statements must include the name of the subprogram and a list of parameters to be bound to
the formal parameters of the subprogram. These parameters are called actual parameters.
The advantage of keyword parameters is that they can appear in any order in the actual
parameter list.
The disadvantage to keyword parameters is that the user of the subprogram must know the
names of formal parameters.
Fundamentals of Subprograms:
General Characteristics of Subprogram:
Each subprogram has a single entry point.
The calling program unit is suspended during the execution of the called subprogram, which
implies that there is only one subprogram in execution at any given time.
Control always returns to the caller when the subprogram execution terminates.
Basic Definitions
A subprogram is a self-contained program which contains block of statements to perform
specific task. It describes the interface to and the actions of the subprogram abstraction.
A subprogram call is the explicit request that a specific subprogram be executed.
A subprogram is said to be active if, after having been called, it has begun execution but has
not yet completed that execution.
A subprogram header is the first part of the definition that can be used for several
purposes.
o First, it specifies the subprogram definition for a particular kind. In languages that
have more than one kind of subprogram, the kind of the subprogram is usually
specified with a special word.
o Second, if the subprogram is not anonymous, the header provides a name for the
subprogram.
o Third, it may optionally specify a list of parameters.
Consider the following header examples:
def adder parameters): This is the header of a Python subprogram named adder. Ruby
subprogram headers also begin with def. The header of a JavaScript subprogram begins with
function.
In C, the header of a function named adder might be as follows:
2. Prepared by B.R.S.S. RAJU Page 2
void adder (parameters)
The reserved word void in this header indicates that the subprogram does not return a value.
The body of subprograms defines its actions.
In the C-based languages (and some others. For example, JavaScript) the body of a
subprogram is delimited by braces. In Ruby, an end statement terminates the body of a
subprogram.
Procedures and Functions
Functions return values and procedures do not.
float power(float base, float exp)
which could be called with result = 3.4 * power(10.0, x).
Design Issues for Subprograms
Subprograms are complex structures in programming languages, and it follows from this that a
lengthy list of issues is involved in their design.
1. What parameter passing methods are provided?
2. Are parameter types checked?
3. Are local variables static or dynamic?
4. What is the referencing environment of a passed subprogram?
5. Are parameter types in passed subprograms checked?
6. Can subprogram definitions be nested?
7. Can subprograms be overloaded?
8. Are subprograms allowed to be generic?
9. Is separate or independent compilation supported?
Local Referencing Environments
Subprograms can define their own variables, thereby defining local referencing environments.
Variables that are defined inside subprograms are called local variables, because their scope is
usually the body of the subprogram in which they are defined.
int adder(int list[], int listlen) {
static int sum = 0;
int count;
for (count = 0; count < listlen; count ++)
sum += list [count];
return sum;
}
In the above example, Local variables are sum and count.
Local variables can be either static or stack dynamic. If local variables are stack dynamic,
they are bound to storage when the subprogram begins execution and are unbound from
storage when that execution terminates.
There are several advantages of stack-dynamic local variables, the primary one being the
flexibility they provide to the subprogram. It is essential that recursive subprograms have stack-
dynamic local variables.
Another advantage of stack-dynamic locals is that the storage for local variables in an active
subprogram can be shared with the local variables in all inactive subprograms. This is not as great
an advantage as it was when computers had smaller memories.
What are the advantages and disadvantages of dynamic local variable?
The main disadvantages of stack-dynamic local variables are the following:
3. Prepared by B.R.S.S. RAJU Page 3
First, there is the cost of the time required to allocate, initialize (when necessary), and
deallocate such variables for each call to the subprogram.
Second, accesses to stack-dynamic local variables must be indirect, whereas accesses to
static variables can be direct. This indirectness is required because the place in the stack
where a particular local variable will reside can be determined only during execution.
Finally, when all local variables are stack dynamic, subprograms cannot be history sensitive;
that is, they cannot retain data values of local variables between calls.
What are the advantages and disadvantages of static local variable?
The primary advantage of static local variables over stack-dynamic local variables is that
they are slightly more efficient. They require no run-time overhead for allocation and
deallocation. Also, if accessed directly, these accesses are obviously more efficient. And, of
course, they allow subprograms to be history sensitive.
The greatest disadvantage of static local variables is their inability to support recursion.
Also, their storage cannot be shared with the local variables of other inactive subprograms.
Eg: int adder(int list[], int listlen) {
static int sum = 0;
int count;
for (count = 0; count < listlen; count ++)
sum += list [count];
return sum; }
Parameter-Passing Methods
Parameter-passing methods are the ways in which parameters are transmitted to and/or from called
subprograms.
Semantic models:
What are three semantic models of parameter passing?
Formal parameters are characterized by one of three distinct semantics models:
(1) They can receive data from the corresponding actual parameter; (2) they can transmit data to the
actual parameter; or (3) they can do both. These models are called in mode, out mode, and inout
mode, respectively.
For example, consider a subprogram that takes two arrays of int values as parameters—list1 and
list2.
The subprogram must add list1 to list2 and return the result as a revised version of list2.
Furthermore, the subprogram must create a new array from the two given arrays and return it.
For this subprogram, list1 should be in mode, because it is not to be changed by the
subprogram. list2 must be inout mode, because the subprogram needs the given value of the
array and must return its new value. The third array should be out mode, because there is no
initial value for this array and its computed value must be returned to the caller.
There are two conceptual models of how data transfers take place in parameter transmission:
Either an actual value is copied (to the caller, to the called, or both ways), or an access path is
transmitted. Figure illustrates the three semantics models of parameter passing when values are
copied.
4. Prepared by B.R.S.S. RAJU Page 4
ParameterPassing:
What is the parameter-passing method of Python and Ruby called?
The parameter-passing method of Python and Ruby is called pass-by assignment. Because all data
values are objects, every variable is a reference to an object. In pass-by-assignment, the actual
parameter value is assigned to the formal parameter.
Implementation Models:
Pass-by-value (in mode)
Pass-by-value is normally implemented by copy, because it is more efficient. It could be
implemented by transmitting an access path to the value of the actual parameter in the caller, but
that would require that the value be in a write-protected cell (one that can only be read).
For example, suppose the subprogram to which the parameter was passed passes it in turn to another
subprogram.
- Either by physical move or access path
- Disadvantages of access path method:
- Must write-protect in the called subprogram
- Accesses cost more (indirect addressing)
- Disadvantages of physical move:
- Requires more storage
- Cost of the moves
Pass-by-result(out mode)
Pass-by-result is an implementation model for out-mode parameters. When a parameter is passed
by result, no value is transmitted to the subprogram. The corresponding formal parameter acts as a
local variable, but just before control is transferred back to the caller; its value is transmitted back to
the caller’s actual parameter, which obviously must be a variable.
- Advantages:
- Local’s value is passed back to the caller
- Physical move is usually used
- Disadvantages:
a. If value is passed, time and space
b. In both cases, order dependence may be a problem
5. Prepared by B.R.S.S. RAJU Page 5
e.g.
procedure sub1(y: int, z: int);
...
sub1(x, x);
Value of x in the caller depends on order of assignments at the return
Pass-by-value-result(inout mode)
Pass-by-value-result is an implementation model for inout-mode parameters in which actual values
are copied. It is a combination of pass-by-value and pass-by-result. The value of the actual
parameter is used to initialize the corresponding formal parameter, which then acts as a local
variable.
In fact, pass-by-value-result formal parameters must have local storage associated with the called
subprogram. At subprogram termination, the value of the formal parameter is transmitted back to
the actual parameter.
-Advantages:
- Physical move, both ways
- Also called pass-by-copy
- Disadvantages:
- Those of pass-by-result
- Those of pass-by-value
Pass-by-reference(inout mode)
Pass-by-reference is a second implementation model for inout-mode parameters. Rather than
copying data values back and forth, however, as in pass-by-value-result, the pass-by-reference
method transmits an access path, usually just an address, to the called subprogram.
This provides the access path to the cell storing the actual parameter. Thus, the called subprogram is
allowed to access the actual parameter in the calling program unit. In effect, the actual parameter is
shared with the called subprogram.
- Pass an access path
- Also called pass-by-sharing
- Advantage:
- passing process itself is efficient.
- Disadvantages:
a. Slower accesses
b. Can allow aliasing:
i. Actual parameter collisions:
e.g.
procedure sub1(a: int, b: int);
...
sub1(x, x);
ii. Array element collisions:
e.g.
sub1(a[i], a[j]); /* if i = j */
Also, sub2(a, a[i]);
Implementing Parameter-Passing Methods:
The implementation of passby-value, -result, -value-result, and -reference, where the run-time stack
is used, is shown in Figure. Subprogram sub is called from main with the call sub(w, x, y, z), where
w is passed by value, x is passed by result, y is passed by value-result, and z is passed by reference.
6. Prepared by B.R.S.S. RAJU Page 6
Overloaded Subprograms:
An overloaded subprogram is a subprogram that has the same name as another
subprogram in the same referencing environment.
The meaning of a call to an overloaded subprogram is determined by the actual parameter
list (and/or possibly the type of the returned value, in the case of a function). Although it is
not necessary, overloaded subprograms usually implement the same process.
C++, Java, Ada, and C# include predefined overloaded subprograms. For example, many
classes in C++, Java, and C# have overloaded constructors.
For example, if a C++ program has two functions named fun and both take an int parameter
but one returns an int and one returns a float, the program would not compile, because the
compiler could not determine which version of fun should be used.
For example, consider the following C++ code:
void fun(float b = 0.0);
void fun();
. . .
fun();
The call is ambiguous and will cause a compilation error.
Generic Subprograms:
A generic or polymorphic subprogram is one that takes parameters of different types on different
activations.
A subprogram that takes a generic parameter that is used in a type expression that describes the type
of the parameters of the subprogram provides parametric polymorphism
Generic functions in C++ have the descriptive name of template functions. The definition of a
template function has the general form
template <template parameters>
a function definition that may include the template parameters
A template parameter (there must be at least one) has one of the forms
class identifier
7. Prepared by B.R.S.S. RAJU Page 7
typename identifier
The class form is used for type names. The typename form is used for passing a value to the
template function. For example, it is sometimes convenient to pass an integer value for the size of
an array in the template function.
A template can take another template, in practice often a template class that defines a user-defined
generic type, as a parameter, but we do not consider that option here.
As an example of a template function, consider the following:
template <class Type>
Type max(Type first, Type second) {
return first > second ? first : second;
}
where Type is the parameter that specifies the type of data on which the function will operate.
This template function can be instantiated for any type for which the operator > is defined. For
example, if it were instantiated with int as the parameter, it would be
int max(int first, int second) {
return first > second ? first : second;
}
Generic Methods in Java 5.0
Generic types and methods was added to Java in Java 5.0. The name of a generic class in Java 5.0 is
specified by a name followed by one or more type variables delimited by pointed brackets.
For example,
generic_class<T>
where T is the type variable.
As an example of a generic Java 5.0 method, consider the following skeletal method definition:
public static <T> T doIt(T[] list) {
. . .
}
This defines a method named doIt that takes an array of elements of a generic type. The name of the
generic type is T and it must be an array. Following is an example call to doIt:
doIt<String>(myList);
Generic Methods in C# 2005
The generic methods of C# 2005 are similar in capability to those of Java 5.0, except there is no
support for wildcard types.
For example, consider the following
skeletal class definition:
class MyClass {
public static T DoIt<T>(T p1) {
. . .
} }
Design Issues for Functions
The following design issues are specific to functions:
• Are side effects allowed?
• What types of values can be returned?
• How many values can be returned?
8. Prepared by B.R.S.S. RAJU Page 8
Functional Side Effects
Ada functions can have only in-mode formal parameters. This requirement effectively prevents a
function from causing side effects through its parameters. In most other imperative languages,
however, functions can have either pass-by-value or pass-by-reference parameters, thus allowing
functions that cause side effects and aliasing.
a. Two-way parameters (Ada does not allow)
b. Nonlocal reference (all allow).
Types of Returned Values
Ada, Python, Ruby, and Lua are the only languages among current imperative languages whose
functions (and/or methods) can return values of any type. C allows any type to be returned by its
functions except arrays and functions.
In some programming languages, subprograms are first-class objects, which means that they can be
passed as parameters, returned from functions, and assigned to variables.
Number of Returned Values
In most languages, only a single value can be returned from a function. Ruby allows the
return of more than one value from a method.
If a return statement in a Ruby method is not followed by an expression, nil is returned. If
followed by one expression, the value of the expression is returned.
If followed by more than one expression, an array of the values of all of the expressions is
returned.
Lua also allows functions to return multiple values. Such values follow the return statement
as a comma-separated list, as in the following:
return 3, sum, index
The form of the statement that calls the function determines the number of values that are received
by the caller. If the function is called as a procedure, that is, as a statement, all return values are
ignored. If the function returned three values and all are to be kept by the caller, the function would
be called as in the following example:
a, b, c = fun()
The General Semantics of Calls and Returns
The subprogram call and return operations are together called subprogram linkage. The
implementation of subprograms must be based on the semantics of the subprogram linkage of the
language being implemented.
A subprogram call in a typical language has numerous actions associated with it. The call
process must include the implementation of whatever parameter-passing method is used. If local
variables are not static, the call process must allocate storage for the locals declared in the called
subprogram and bind those variables to that storage.
It must save the execution status of the calling program unit. The execution status is
everything needed to resume execution of the calling program unit. This includes register values,
CPU status bits, and the environment pointer (EP).
The EP is used to access parameters and local variables during the execution of a subprogram. The
calling process also must arrange to transfer control to the code of the subprogram and ensure that
control can return to the proper place when the subprogram execution is completed.
Finally, if the language supports nested subprograms, the call process must create some mechanism
to provide access to nonlocal variables that are visible to the called subprogram.
9. Prepared by B.R.S.S. RAJU Page 9
Implementing “Simple” Subprograms
Implementing simple subprograms by “simple” subprograms cannot be nested and all local
variables are static.
The semantics of a call to a “simple” subprogram requires the following actions:
1. Save the execution status of the current program unit.
2. Compute and pass the parameters.
3. Pass the return address to the called.
4. Transfer control to the called.
The semantics of a return from a simple subprogram requires the following actions:
1. If there are pass-by-value-result or out-mode parameters, the current values of those
parameters are moved to or made available to the corresponding actual parameters.
2. If the subprogram is a function, the functional value is moved to a place accessible to the
caller.
3. The execution status of the caller is restored.
4. Control is transferred back to the caller.
The call and return actions require storage for the following:
• Status information about the caller
• Parameters
• Return address
• Return value for functions
• Temporaries used by the code of the subprograms.
A simple subprogram consists of two separate parts:
The actual code of the subprogram, which is constant, and the local variables and data listed
previously, which can change when the subprogram is executed. In the case of simple subprograms,
both of these parts have fixed sizes.
The format, or layout, of the noncode part of a subprogram is called an activation record, because
the data it describes are relevant only during the activation, or execution of the subprogram. The
form of an activation record is static. An activation record instance is a concrete example of an
activation record, a collection of data in the form of an activation record. One possible layout for
activation records is shown in Figure.
The following Figure shows a program consisting of a main program and three subprograms: A, B,
and C. Although the figure shows all the code segments separated from all the activation record
instances, in some cases, the activation record instances are attached to their associated code
segments.
10. Prepared by B.R.S.S. RAJU Page 10
Implementing Subprograms with Stack-Dynamic Local Variables
Subprogram linkage in languages that use stack-dynamic local variables are more complex than the
linkage of simple subprograms for the following reasons:
The compiler must generate code to cause the implicit allocation and deallocation of local
variables.
Recursion adds the possibility of multiple simultaneous activations of a subprogram, which
means that there can be more than one instance (incomplete execution) of a subprogram at a
given time, with at least one call from outside the subprogram and one or more recursive
calls. The number of activations is limited only by the memory size of the machine. Each
activation requires its activation record instance.
The format of an activation record for a given subprogram in most languages is known at compile
time. In many cases, the size is also known for activation records because all local data are of a
fixed size. The typical activation record for such a language is shown in Figure.
Consider the following skeletal C function:
void sub(float total, int part) {
int list[5];
float sum;
. . .
}
11. Prepared by B.R.S.S. RAJU Page 11
The activation record for sub is shown in Figure.
An Example Without Recursion
Consider the following skeletal C program:
void fun1(float r) {
int s, t;
. . . 1
fun2(s);
. . .
}
void fun2(int x) {
int y;
. . . 2
fun3(y);
. . .
}
void fun3(int q) {
. . . 3
}
void main() {
float p;
. . .
fun1(p);
. . .
}
The sequence of function calls in this program is
main calls fun1
fun1 calls fun2
fun2 calls fun3
The stack contents for the points labeled 1, 2, and 3 are shown in Figure.
12. Prepared by B.R.S.S. RAJU Page 12
Recursion: Recursion is a function that itself called during the process.
Consider the following example C program, which uses recursion to compute the factorial function:
int factorial(int n) {
if (n <= 1)
return 1;
else return (n * factorial(n - 1));
}
void main() {
int value;
value = factorial(3);
}
The activation record format for the function factorial is shown in Figure.
13. Prepared by B.R.S.S. RAJU Page 13
Nested Subprograms:
A nested function is a function defined inside another function.
The nested function's name is local to the block where it is defined. For example, here we define a
nested function named square, and call it twice:
foo (double a, double b)
{
double square (double z) { return z * z; }
return square (a) + square (b);
}
The nested function can access all the variables of the containing function that are visible at the
point of its definition. This is called lexical scoping. For example, here we show a nested function
which uses an inherited variable named offset:
bar (int *array, int offset, int size)
{
int access (int *array, int index)
{ return array[index + offset]; }
int i;
/* ... */
for (i = 0; i < size; i++)
/* ... */ access (array, i) /* ... */
}
Nested function definitions are permitted within functions in the places where variable definitions
are allowed; that is, in any block, mixed with the other declarations and statements in the block.
It is possible to call the nested function from outside the scope of its name by storing its address or
passing the address to another function:
hack (int *array, int size)
{
void store (int index, int value)
{ array[index] = value; }
intermediate (store, size);
}
For example, consider the following skeletal Python program:
# Global scope
. . .
def f1():
def f2():
def f3():
. . .
# end of f3
. . .
# end of f2
. . .
# end of f1
The static_depths of the global scope, f1, f2, and f3 are 0, 1, 2, and 3, respectively.
If procedure f3 references a variable declared in f1, the chain_offset of that reference would
be 2 (static_depth of f3 minus the static_depth of f1).
14. Prepared by B.R.S.S. RAJU Page 14
If procedure f3 references a variable declared in f2, the chain_offset of that reference would
be 1.
Blocks
Blocks are a way of solving the scoping problem. A block is a program region containing
definitions of variables and that delimits the regions where these definitions apply.
In C programming language, a block is created using a pair of curly braces. The beginning of the
block is denoted by an open curly brace '{' and the end is denoted by a closing curly brace '}'. The
block collects statements together into a single compound statement.
The C-based languages, provide for user-specified local scopes for variables called blocks.
As an example of a block, consider the following code segment:
{ int temp;
temp = list[upper];
list[upper] = list[lower];
list[lower] = temp;
}
In java, a block is a group of zero or more statements between balanced braces and can be used
anywhere a single statement is allowed. The following example, BlockDemo, illustrates the use of
blocks:
class BlockDemo {
public static void main(String[] args) {
boolean condition = true;
if (condition) { // begin block 1
System.out.println("Condition is true.");
} // end block one
else { // begin block 2
System.out.println("Condition is false.");
} // end block 2
}
}
For this program, the static-memory layout shown in Figure could be used.
15. Prepared by B.R.S.S. RAJU Page 15
Implementing Dynamic Scoping
Scoping itself is how you search for a variable with a given name. A variable has a scope which is
the whole area in which that variable can be accessed by name. If there is a reference to a variable
"a" then how does the compiler or interpreter find it?
In dynamic scoping, by contrast, you search in the local function first, then you search in the
function that called the local function, then you search in the function that called that function, and
so on, up the call stack. "Dynamic" refers to change, in that the call stack can be different every
time a given function is called, and so the function might hit different variables depending on where
it is called from.
Dynamic scoping is useful as a substitute for globally scoped variables. A function can say "let
current_numeric_base = 16; call other functions;" and the other functions will all print in
hexadecimal. Then when they return, and the base-setting function returns, the base will return to
whatever it was.
Deep binding - Dynamic scoping is fairly easy to implement. To find an identifier's value, the
program can traverse the runtime stack, checking each activation record (each function's stack
frame) for a value for the identifier. This is known as deep binding.
Consider the following example skeletal program:
void sub3() {
int x, z;
x = u + v;
. . .
}
void sub2() {
int w, x;
. . .
}
void sub1() {
int v, w;
. . .
}
void main() {
int v, u;
. . .
}
This program is written in a syntax that gives it the appearance of a program in a C-based language,
but it is not meant to be in any particular language.
Suppose the following sequence of function calls occurs:
main calls sub1
sub1 calls sub1
sub1 calls sub2
sub2 calls sub3
This is shown in fig.
16. Prepared by B.R.S.S. RAJU Page 16
Stack of dynamic
Shallow binding - An alternate strategy that is usually more efficient is to maintain a stack of
bindings for each identifier; the stack is modified whenever the variable is bound or unbound, and a
variable's value is simply that of the top binding on the stack. This is called shallow binding.
Figure shows the variable stacks for the earlier example program in the same situation as shown
with the stack in above Figure.