CSE332S Object-Oriented Programming in C++ (Lecture 5)
Function and the Call Stack
Function lifecycle
Read variable declaration from right to left
eg:
int i; // i is a integer variable
int & r = i; // r is a reference to i
int *p = &i; // p is a pointer to i
const int * const q = &i; // q is a constant pointer to a constant integerRead function declaration from inside out
eg:
int f(int x); // f is a function that takes an integer argument and returns an integerCpp use the “program call stack” to manage active function invocations
When a function is called:
- A stack frame is “pushed” onto the call stack
- Execution jumps fro the calling functio’s code block to the called function’s code block
Then the function is executed the return value is pushed onto the stack
When a function returns:
- The stack frame is “popped” off the call stack
- Execution jumps back to the calling function’s code block
The compiler manages the program call stack
- Small performance overhead associated with stack frame management
- Size of a stack frame must be known at compile time - cannot allocate dynamically sized objects on the stack
Stack frame
A stack frame represents the state of an active function call
Each frame contains:
- Automatic variables - variables local to the function. (automatic created and destroyed when the function is called and returns)
- Parameters - values passed to the function
- A previous frame pointer - used to access the caller’s frame
- Return address - the address of the instruction to execute after the function returns
Recursion for free
An example of call stack:
void f(int x) {
int y = x + 1;
}
void main(int argc, char *argv[]) {
int z = 1;
f(z);
}when f is called, a stack frame is pushed onto the call stack:
- function
f- parameter
x - return address
- parameter
- function
main- parameter
argc - parameter
argv - return address
- parameter
On recursion, the call stack grows for each recursive call, and shrinks when each recursive call returns.
void f(int x) {
if (x > 0) {
f(x - 1);
}
}
int main(int argc, char *argv[]) {
f(10);
}The function stack will look like this:
- function
f(0)- parameter
x - return address
- parameter
- function
f(1)- parameter
x - return address
- parameter
- …
- function
f(10)- parameter
x - return address
- parameter
- function
main- parameter
argc - parameter
argv - return address
- parameter
Pass by reference and pass by value
However, when we call recursion with pass by reference.
void f(int & x) {
if (x > 0) {
f(x - 1);
}
}
int main(int argc, char *argv[]) {
int z = f(10);
}The function stack will look like this:
- function
f(0)- return address
- function
f(1)- return address
- …
- function
f(10)- return address
- function
main- parameter
z - parameter
argc - parameter
argv - return address
- parameter
This is because the reference is a pointer to the variable, so the function can modify the variable directly without creating a new variable.
Function overloading and overload resolution
Function overloading is a feature that allows a function to have multiple definitions with the same name but different parameters.
Example:
void errMsg(int &x){
cout << "Error with code: " << x << endl;
}
void errMsg(const int &x){
cout << "Error with code: " << x << endl;
}
void errMsg(const string &x){
cout << "Error with message: " << x << endl;
}
void errMsg(const int &x, const string &y){
cout << "Error with code: " << x << " and message: " << y << endl;
}
int main(int argc, char *argv[]){
int x = 10;
const int y = 10;
string z = "File not found";
errMsg(x); // this is the first function (best match: int to int)
errMsg(y); // this is the second function (best match: const int to const int)
errMsg(z); // this is the third function (best match: string to const string)
errMsg(x, z); // this is the fourth function (best match: int to const int, string to const string)
}When the function is called, the compiler will automatically determine which function to use based on the arguments passed to the function.
BUT, there is still ambiguity when the function is called with the same type of arguments.
void errMsg(int &x);
void errMsg(short &x);
int main(int argc, char *argv[]){
char x = 'a';
errMsg(x); // this is ambiguous, cpp don't know which function to use since char can both be converted to int and short. This will throw an error.
}Default arguments
Default arguments are arguments that are provided by the function caller, but if the caller does not provide a value for the argument, the function will use the default value.
void errMsg(int x = 0, string y = "Unknown error");If the caller does not provide a value for the argument, the function will use the default value.
errMsg(); // this will use the default value for both arguments
errMsg(10); // this will use the default value for the second argument
errMsg(10, "File not found"); // this will use the provided value for both argumentsOverloading and default arguments
void errMsg(int x = 0, string y = "Unknown error");
void errMsg(int x);This is ambiguous, because the compiler don’t know which function to use. This will throw an error.
We can only default the rightmost arguments
void errMsg(int x = 0, string y = "Unknown error");
void errMsg(int x, string y = "Unknown error"); // this is valid
void errMsg(int x = 0, string y); // this is invalidCaller must supply leftmost arguments first, even they are same as default arguments
void errMsg(int x = 0, string y = "Unknown error");
int main(int argc, char *argv[]){
errMsg("File not found"); // this will throw an error, you need to provide the first argument
errMsg(10, "File not found"); // this is valid
}