Unit 3
Unit 3
1. Fundamentals of Subprograms
Definition:
Characteristics of Subprograms:
2. Caller is Suspended:
4. Reusability:
Types of Subprograms:
1. Procedures:
o Examples:
▪ In C:
void printMessage() {
printf("Hello, World!\n");
}
▪ In Python:
def greet():
print("Hello, World!")
2. Functions:
o Examples:
▪ In C:
return a + b;
▪ In Python:
return a + b
1. Modularity:
2. Code Reusability:
3. Readability:
4. Ease of Maintenance:
6. Supports Recursion:
1. Declaration:
o Example:
2. Call:
o Example:
3. Execution:
o The control transfers to the subprogram, and its statements are executed.
4. Return:
5. Termination:
#include <stdio.h>
void greet() {
printf("Hello, World!\n");
}
int add(int a, int b) {
return a + b;
int main() {
return 0;
1. Parameter Passing
o Defines how data is shared between the caller and the subprogram.
o Methods:
2. Return Values
3. Examples:
return a + b;
return a // b, a % b
o Examples:
• Static Variables:
• void counter() {
• static int count = 0; // Retains value across calls
• count++;
• printf("%d\n", count);
• }
• Dynamic Variables for recursion:
• def factorial(n):
• if n == 0:
• return 1
• return n * factorial(n - 1)
5. Exception Handling
6. Recursion
o Allows a subprogram to call itself.
7. Subprogram Overloading
o Examples: Function overloading (e.g., C++ int add(int, int) and double
add(double, double)).
8. Generic Subprograms
o Issue: Improves reusability, but too much flexibility may cause unsafe
behavior.
• For example, when you're writing code, the variables that are defined in the
current function or block of code, as well as those that are available from other
surrounding parts of the program, make up the referencing environment for that
code.
Static Scoping:
• In static scoping, a variable's visibility (or which part of the program can use it)
is determined by where it is written in the code.
• For example, if you define a variable inside a function, that variable is visible only
inside that function and in any nested functions, but not outside.
2. All variables that are visible from outer (enclosing) functions or blocks
where the statement is located.
Dynamic Scoping:
1. Local variables.
Active Subprogram:
• Static variables are allocated and initialized once, and they stay in memory for
the entire program execution.
• How it works: When you pass a parameter by value, the value of the variable is
copied to the called subprogram (function). The called function works with the
copy, and any changes made to the parameter inside the function do not
affect the original variable.
• Example: If you pass a number x = 5 to a function, the function only gets a copy
of x, so even if the function changes the value, the original x remains unchanged.
• def add_one(x):
return x
num = 5
result = add_one(num) # num remains 5 after the function call
2. Pass-by-Result:
• How it works: The called function doesn’t receive the original variable
directly. Instead, the function gets a temporary space to store the result. After
the function finishes executing, the result (the final value of the parameter) is
copied back to the original variable.
• Example: If you pass x = 5, the function makes changes to a copy of x, and when
the function finishes, the updated value is copied back into the original x.
def multiply_by_two(x):
return x
num = 5
3. Pass-by-Value-Result:
o Then, after the function finishes, the result is passed back to the original
variable.
• Example:
def increment_and_return(x):
return x
num = 5
4. Pass-by-Reference:
• How it works: In pass-by-reference, the called subprogram gets the actual
memory location (reference) of the parameter, not just a copy. This means that
any changes made to the parameter inside the function will directly affect
the original variable.
o Instead of a copy, the function works directly with the original value.
• Example:
def add_five(x):
return x
num = 5
5. Pass-by-Name:
• Example: In languages like Algol, this would mean passing code or expressions
that will be evaluated when used in the function.
def add_something(expr):
return expr + 5
• Pass-by-value by default.
3. C++:
4. Java:
• Object parameters are passed by reference (i.e., the reference to the object is
passed).
5. Ada:
6. C#:
• Pass-by-value is default.
• Pass-by-reference is done by using the ref keyword in both the formal parameter
and actual parameter.
7. PHP:
8. Perl:
• Reliability: Type checking helps catch errors early in the development process
by ensuring that data is being used correctly, making the program more reliable.
• Safety: Without type checking, mismatched types (e.g., passing a string where a
number is expected) could lead to unexpected behavior or crashes.
separately compiled, the compiler needs to know the declared size of that array
• Programmer is required to include the declared sizes of all but the first subscript
in the actual parameter
• Disallows writing flexible subprograms
• Solution: pass a pointer to the array and the sizes of the dimensions as other
parameters; the user must include the storage mapping function in terms of the
size parameters
Multidimensional Arrays as Parameters: Pascal and Ada
Pascal
Ada
Formal parameter that are arrays have a declaration after the header
function
• Similar to Ada
• Arrays are objects; they are all single-dimensioned, but the elements can be
arrays
• Each array inherits a named constant (length in Java, Length in C#) that is setto
the length of the array when the array object is created
1. Efficiency:
• Pass-by-value, on the other hand, involves copying the entire data, which may
be less efficient for large data structures.
• Two-way data transfer happens when data is both read and modified by the
function.
Parameters as subprograms:
In some programming languages, it’s useful to pass subprograms (functions or
procedures) as parameters to other subprograms. This allows for more flexible and
dynamic behavior, such as using a function as an argument in another function, or
passing custom operations to be executed within a given context.
• Flexibility: Allows you to write more general and reusable code, where you can
pass different operations or behaviors to functions.
When subprograms are passed as parameters, there are several key issues that need to
be addressed:
o Solution:
In C and C++, you can use function pointers to store the address of a function and then
call it indirectly.
• How it works: A function pointer holds the memory address of a function, and
you can use this pointer to invoke the function at a later time.
In languages like Python or JavaScript, functions are first-class citizens, meaning you
can treat functions as objects. You can store them in variables, pass them as
parameters, and invoke them dynamically.
3. Callbacks:
• How it works: The "caller" function accepts another function (the callback) as a
parameter and calls it at the appropriate time.
In object-oriented languages (like Java, C++, or Python), you can call methods on
objects indirectly using references or pointers.
• How it works: An object reference can point to a method, and that method can
be invoked indirectly.
Some languages (like Java and Python) allow reflection or introspection, which
enables you to get information about classes and methods at runtime. You can use this
feature to call methods indirectly by their names.
• How it works: You can dynamically find and invoke methods based on their
names as strings, even if the methods aren’t directly referenced in the code.
Polymorphism in Subprograms:
• Polymorphism refers to the ability to use a single interface or function with
different types of data.
Types of Polymorphism:
Overloaded Subprograms:
An overloaded subprogram is one that has the same name as another subprogram in
the same referencing environment. This allows multiple versions of the same
subprogram to coexist, each providing different functionality based on the arguments
passed to them.
Key Concepts:
o Or a combination of both.
1. C++:
o The compiler selects the correct function version based on the arguments
passed at the call.
2. Java:
o Java also supports method overloading, where multiple methods with the
same name can exist, but the number or type of their parameters must
differ.
3. C#:
o C# allows method overloading where the same method name can have
different signatures (based on the number or types of arguments).
4. Ada:
Advantages of Overloading:
1. Improved Readability: You can use the same name for similar functions that
operate on different data types, improving code clarity.
2. Code Reusability: Overloading allows you to reuse the same function name for
different tasks based on the arguments provided.
3. Flexibility: It allows the programmer to write flexible and concise code without
creating numerous differently named functions.
Challenges:
Generic Subprograms:
A generic subprogram (also called a generic function or template function) is a
subprogram that can accept parameters of different types on different calls. This
allows the same subprogram to be used with multiple types without needing to define
multiple versions of the subprogram for each type.
• Example:
C++ template
Type max(Type first, Type second)
C++ (Templates):
In C++, you can define a generic function using templates. A template is a way to write
a function that can work with any data type.
Java (Generics):
In Java, generics are used to define generic subprograms (methods or classes) that
work with any data type.
Ada (Generics):
In Ada, generic subprograms (generic procedures and functions) allow you to define a
subprogram that can work with any data type.
C# (Generics):
In C#, generics are used to create methods that can accept different data types.
1. Reusability: You can use the same subprogram with different types, avoiding the
need to write multiple versions of the same function.
2. Type Safety: Generic subprograms allow for type checking at compile time,
ensuring that type-related errors are caught early.
3. Flexibility: Generic subprograms provide the flexibility to work with various types
without the need for casting or converting data types.
Side effects refer to any changes a function makes to variables or data outside its own
scope, such as modifying global variables, changing the state of input parameters, or
interacting with external systems (e.g., writing to a file, printing to the console).
The return type of a function determines what kind of value the function will return to
the caller. This can vary significantly across programming languages, and the design of
the function depends on what types are allowed or restricted.
1. C:
o A function can return any primitive type (like int, float, char, etc.) or
pointers to data structures, but arrays and functions cannot be directly
returned.
Example:
int* getArray() {
2. C++:
3. Ada:
o In Ada, the return type of a function can be any type, including user-
defined types (like records, arrays, etc.).
4. Java:
o In Java, methods (functions are called methods in Java) can return any
type, but Java does not support functions (in the sense of C-style
functions). Instead, Java uses methods that belong to classes.
Coroutines:
A coroutine is a type of subprogram (a function or procedure) that has:
This is why coroutines are often referred to as implementing symmetric control, where
the caller and the called coroutine are treated as equals.
1. Resumable Execution:
o A coroutine can pause its execution at a specific point and later resume
from where it left off.
o This contrasts with regular functions, which always restart execution from
the beginning when called.
2. Resume Mechanism:
o The first call to a coroutine starts its execution from the beginning.
o Subsequent calls (known as resumes) continue from the point just after
the last executed statement.
3. Quasi-Concurrent Execution:
o Coroutines enable a form of cooperative multitasking. Multiple
coroutines can share control and pass execution back and forth between
themselves without overlapping their execution.
o This differs from threads, which can execute concurrently and in parallel.
4. Looping Execution:
2. When it encounters a pause or yield, it saves its current state (local variables
and the point of execution) and transfers control back to the caller.
3. On subsequent resume calls, it restores its state and continues execution from
the saved point.
Example:
a, b = 0, 1
while True:
When fibonacci() is called and resumed, it produces the next Fibonacci number without
starting over.
Advantages of Coroutines
2. Efficient Execution:
4. Cooperative Multitasking:
1. Parameter Passing:
o The process must handle how parameters (inputs/outputs) are passed to
the subprogram (e.g., by value or reference).
o If the subprogram has temporary variables (local variables) that are not
fixed (static), memory must be allocated for them during the call.
o The program must save everything needed to resume execution after the
subprogram finishes. This includes:
4. Transferring Control:
o The program jumps to the starting point of the subprogram and ensures it
can return to the correct spot in the caller after the subprogram finishes.
1. Updating Parameters:
o If parameters are passed using out mode or inout mode (where values
can be changed), the updated values are copied back to the original
variables.
4. Returning Control:
o The program jumps back to the location in the caller where the
subprogram was initially called.
• Key Features:
o Fixed memory layout: Local variables and activation records do not grow
or shrink during execution.
o Single instance: Only one version of the subprogram is active at any time.
o This ensures the program can resume where it left off after the
subprogram finishes.
o Store the memory address of the instruction in the caller where execution
will resume.
4. Transfer Control:
4. Return Control:
-Storage Requirements:
1. Status Information:
2. Parameters:
o Memory for input and output parameters passed during the call.
3. Return Address:
5. Temporaries:
-Activation Record
• Contents:
o Parameters.
o Local variables.
o Return address.
o Temporaries.
• Fixed Size:
o In simple subprograms, the activation record size is known at compile
time, allowing static allocation.
-Advantages:
o Efficient execution.
-Limitations:
1. Stack-based Allocation:
o Once the subprogram completes, the AR is popped from the stack, and
the local variables go out of scope and are destroyed.
Let’s walk through how to implement a function with stack-dynamic local variables.
Step-by-Step Example in C
Consider a simple program with a subprogram (function) that uses local variables
dynamically allocated on the stack.
#include <stdio.h>
void subprogram(int x) {
int y = 10;
int z = x + y;
int main() {
int a = 5;
return 0;
Initially, when the main() function is executing, there are local variables (a) for main().
The stack looks like this:
|----------------------|
|----------------------|
|----------------------|
|----------------------|
|----------------------|
|----------------------|
3. Function Execution
• Local Variables:
o y is initialized to 10.
Once subprogram() finishes execution, its AR is popped from the stack, and its local
variables (y and z) are destroyed. The stack now only contains the AR for main():
|----------------------|
|----------------------|
5. Memory Deallocation
Since the variables y and z were stack-dynamic, they were automatically deallocated
when subprogram() finished. The memory that was allocated for them is released, and
there’s no need for explicit memory management (like free() in some languages).
2. Access to Outer Variables: Inner subprograms can access variables that are
defined in the enclosing (outer) subprogram. This can lead to more flexible
designs, but also introduces challenges in variable scope resolution and access
to non-local variables.
6. Example in JavaScript:
javascript
function outer() {
let outerVar = 10;
function inner() {
inner();
In this example:
7. Activation Records:
8. Static Links:
Blocks
A block is a section of code enclosed in curly braces {} that defines a local scope for
variables. It allows variables to be used only inside the block and ensures they don't
interfere with other variables in the program.
Example in C:
{
int temp;
temp = list[upper];
list[upper] = list[lower];
list[lower] = temp;
o Here, temp is only accessible inside the block and doesn't affect other
variables named temp elsewhere in the program.
2. Variable Lifetime:
o Variables declared inside a block exist only while the block is being
executed.
o Once control exits the block, the variables are no longer available.
4. Example:
5. Memory Management:
o When a block ends, its variables are removed, and their memory can be
reused by other blocks. This helps manage memory efficiently.
Advantages of Blocks:
1. Scope Control: Variables inside a block can't be accessed outside it, reducing
conflicts.
2. Efficient Memory Usage: Memory for block variables is reused when the block
ends.
3. No Interference: You can use the same variable names in different blocks
without interference.
Blocks are useful for keeping variables local to a specific part of the code, reducing
interference, and managing memory efficiently.
1. Deep Access
Deep access involves searching through the activation records (the stack frames) of all
active subprograms, starting from the most recently activated one, to find the nonlocal
variable.
• How it works:
o When a variable is referenced, the system looks for the variable starting
from the most recent subprogram call and works backwards through the
stack.
o If the variable is not found in the current subprogram, the system looks in
the previous subprogram's activation record, and so on, until the variable
is found or the stack is exhausted.
• Example:
• void sub3() {
• int x, z;
• }
• void sub2() {
• int w, x;
• }
• void sub1() {
• int v, w;
• }
• void main() {
• int v, u;
• }
If sub3() is called, it looks for u and v by searching the activation records in the order
they were called (from sub3 to main). It will first find x in sub3, then find v in sub1, and u
in main.
• Disadvantages:
2. Shallow Access
• How it works:
o Every time a subprogram is called, the new variable instance for that
name is pushed onto the stack or added to the table.
o When a variable is referenced, the system always uses the most recent
version of the variable (the one at the top of the stack or table).
1. Separate Stack for Each Variable: Each variable name has its own stack, and
the most recent variable is accessed by looking at the top of the stack.
2. Central Table: A single table holds all variable names with an "active" bit to
indicate if the variable is currently in use. The table points to the most recent value of
the variable.
• Disadvantages:
1. Process Abstraction:
o The essential details are the name of the array, its length, and the fact that
the array will be sorted. The algorithm used to sort the array is not
important for the user.
2. Data Abstraction:
Even built-in types like floating-point numbers are abstract data types. For instance,
floating-point numbers in most languages allow you to store and perform operations like
addition, subtraction, multiplication, etc., but hide how the numbers are actually
represented in memory.
Before the IEEE 754 standard for floating-point representation, different computer
architectures used various formats. However, programs could still be portable because
the implementation details were hidden.
1. Hidden Representation: The actual data structure is hidden, and users can only
interact with it through the defined operations.
2. Type Definition and Operations: The type and its operations are packaged
together, forming an interface. This means other parts of the program can use the
type without knowing how it is implemented.
A stack is a common abstract data type that stores elements and allows access to only
the top element. Its abstract operations could include:
For example, the code for using the stack might look like this:
create(stk1);
push(stk1, color1);
push(stk1, color2);
temp = top(stk1);
In this code:
2. Built-in Operations: ADTs should have minimal general operations, with most
operations provided within the type's definition. Common operations like
assignment and equality comparisons might be needed, but not all ADTs will
require them.
4. Encapsulation: Some languages, like C++, Java, and C#, directly support ADTs,
while others (like Ada) offer a more generalized encapsulation, allowing more
flexible definitions.
6. Access Controls: The language must define how to restrict access to the
internal details of an ADT, ensuring that only specified operations can modify the
data.
• Information Hiding:
• Types:
2. C++:
• Information Hiding:
o Private members are hidden, and public methods expose the interface.
3. Java:
• Access Control:
• Example: Java code for stack class with methods for push, pop, etc.
4. C#:
• Based on C++ and Java but adds:
• Memory Management:
Supported Languages:
• C++, Ada, Java 5.0, and C# 2005 provide support for parameterized ADTs.
• Similar to Java, C# supports generic types for parameterized ADTs. These types
are commonly used in collections and can be accessed through indexing.
Encapsulation Constructs
Encapsulation is a way to group related data and functions into a single unit to make
large programs more manageable and avoid unnecessary recompilation. It helps in
organizing code logically and improves reusability.
Encapsulation in C:
• Header files contain declarations, and implementation files contain the actual
definitions. The client includes the header to use these functions and data.
• However, C’s approach has some risks, such as potential mismatches between
the header and implementation files, which could cause errors that the linker
won't catch.
Encapsulation in C++:
• Friend Functions: C++ allows functions (like vector and matrix multiplication) to
access private members of multiple classes, even if they are not part of those
classes, through "friend" declarations.
Encapsulation in Ada:
• Ada Packages provide a powerful form of encapsulation, where you can define
both data types and operations in a single package. This makes it easier to
handle cases like the vector and matrix example in C++.
Encapsulation in C# (Assemblies):
• C# uses Assemblies as the primary encapsulation construct. An assembly is a
file that contains code (in Common Intermediate Language), metadata about
classes, and references to other assemblies.
• Internal Access Modifier: In C#, the internal modifier allows class members to
be visible to other classes within the same assembly.
Naming Encapsulations
In large software systems, naming encapsulations are crucial for organizing code and
avoiding conflicts between names used by different developers or libraries. These
encapsulations create logical units that allow independent parts of the program to
work together without accidentally using the same names for variables, methods, or
classes.
• Java: Java has packages, which help organize classes and avoid naming issues.
• Ada: Ada uses packages to encapsulate names and keep them separate from
other parts of the program.
• Ruby: Ruby uses modules to achieve a similar effect, organizing code into
namespaces.