# CSE332S Object-Oriented Programming in C++ (Lecture 5) ## Function and the Call Stack ### Function lifecycle Read variable declaration from right to left eg: ```c 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 integer ``` Read function declaration from inside out eg: ```c int f(int x); // f is a function that takes an integer argument and returns an integer ``` Cpp use the "**program call stack**" to manage active function invocations When a function is called: 1. A stack frame is "**pushed**" onto the call stack 2. 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: 1. The stack frame is "**popped**" off the call stack 2. 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: ```cpp 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 - function `main` - parameter `argc` - parameter `argv` - return address On recursion, the call stack grows for each recursive call, and shrinks when each recursive call returns. ```cpp 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 - function `f(1)` - parameter `x` - return address - ... - function `f(10)` - parameter `x` - return address - function `main` - parameter `argc` - parameter `argv` - return address ### Pass by reference and pass by value However, when we call recursion with pass by reference. ```cpp 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 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: ```cpp 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. ```cpp 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. ```cpp 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. ```cpp 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 arguments ``` Overloading and default arguments ```cpp 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 ```cpp 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 invalid ``` Caller must supply leftmost arguments first, even they are same as default arguments ```cpp 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 } ```