- 7.1 Introduction
- 7.2 Packaging Code in C#
- 7.3 static Methods, static Variables and Class Math
- 7.4 Methods with Multiple Parameters
- 7.5 Notes on Using Methods
- 7.6 Argument Promotion and Casting
- 7.7 The .NET Framework Class Library
- 7.8 Case Study: Random-Number Generation
- 7.9 Case Study: A Game of Chance; Introducing Enumerations
- 7.10 Scope of Declarations
- 7.11 Method-Call Stack and Activation Records
- 7.12 Method Overloading
- 7.13 Optional Parameters
- 7.14 Named Parameters
- 7.15 C# 6 Expression-Bodied Methods and Properties
- 7.16 Recursion
- 7.17 Value Types vs. Reference Types
- 7.18 Passing Arguments By Value and By Reference
- 7.19 Wrap-Up
7.11 Method-Call Stack and Activation Records
To understand how C# performs method calls, we first need to consider a data structure (i.e., collection of related data items) known as a stack. Think of a stack as analogous to a pile of dishes. When a dish is placed on the pile, it’s placed at the top—referred to as pushing the dish onto the stack. Similarly, when a dish is removed from the pile, it’s removed from the top—referred to as popping the dish off the stack. Stacks are known as last-in, first-out (LIFO) data structures—the last item pushed (inserted) on the stack is the first item popped (removed) from the stack.
7.11.1 Method-Call Stack
The method-call stack (sometimes referred to as the program-execution stack) is a data structure that works behind the scenes to support the method call/return mechanism. It also supports the creation, maintenance and destruction of each called method’s local variables. As we’ll see in Figs. 7.10–7.12, the stack’s last-in, first-out (LIFO) behavior is exactly what a method needs in order to return to the method that called it.
7.11.2 Stack Frames
As each method is called, it may, in turn, call other methods, which may, in turn, call other methods—all before any of the methods return. Each method eventually must return control to the method that called it. So, somehow, the system must keep track of the return addresses that each method needs in order to return control to the method that called it. The method-call stack is the perfect data structure for handling this information. Each time a method calls another method, an entry is pushed onto the stack. This entry, called a stack frame or an activation record, contains the return address that the called method needs in order to return to the calling method. It also contains some additional information we’ll soon discuss. If the called method returns instead of calling another method before returning, the stack frame for the method call is popped, and control transfers to the return address in the popped stack frame. The same techniques apply when a method accesses a property or when a property calls a method.
The beauty of the call stack is that each called method always finds the information it needs to return to its caller at the top of the call stack. And, if a method makes a call to another method, a stack frame for the new method call is simply pushed onto the call stack. Thus, the return address required by the newly called method to return to its caller is now located at the top of the stack.
7.11.3 Local Variables and Stack Frames
The stack frames have another important responsibility. Most methods have local variables—parameters and any local variables the method declares. Local variables need to exist while a method is executing. They need to remain active if the method makes calls to other methods. But when a called method returns to its caller, the called method’s local variables need to “go away.” The called method’s stack frame is a perfect place to reserve the memory for the called method’s local variables. That stack frame exists as long as the called method is active. When that method returns—and no longer needs its local variables—its stack frame is popped from the stack, and those local variables no longer exist.
7.11.4 Stack Overflow
Of course, the amount of memory in a computer is finite, so only a certain amount of memory can be used to store activation records on the method-call stack. If more method calls occur than can have their activation records stored on the method-call stack, a fatal error known as stack overflow occurs2—typically caused by infinite recursion (Section 7.16).
7.11.5 Method-Call Stack in Action
Now let’s consider how the call stack supports the operation of a Square method (lines 15–18 of Fig. 7.9) called by Main (lines 8–12).
1 // Fig. 7.9: SquareTest.cs 2 // Square method used to demonstrate the method 3 // call stack and activation records. 4 using System; 5 6 class Program 7 { 8 static void Main() 9 { 10 int x = 10; // value to square (local variable in main) 11 Console.WriteLine($"x squared: {Square(x)}"); 12 } 13 14 // returns the square of an integer 15 static int Square(int y) // y is a local variable 16 { 17 return y * y; // calculate square of y and return result 18 } 19 }
x squared: 100
Fig. 7.9 | Square method used to demonstrate the method-call stack and activation records.
First, the operating system calls Main—this pushes an activation record onto the stack (Fig. 7.10). This tells Main how to return to the operating system (i.e., transfer to return address R1) and contains the space for Main’s local variable x, which is initialized to 10.
Fig. 7.10 | Method-call stack after the operating system calls main to execute the program.
Method Main—before returning to the operating system—calls method Square in line 11 of Fig. 7.9. This causes a stack frame for Square (lines 15–18) to be pushed onto the method-call stack (Fig. 7.11). This stack frame contains the return address that Square needs to return to Main (i.e., R2) and the memory for Square’s local variable y.
Fig. 7.11 | Method-call stack after Main calls square to perform the calculation.
After Square performs its calculation, it needs to return to Main—and no longer needs the memory for y. So Square’s stack frame is popped from the stack—giving Square the return location in Main (i.e., R2) and losing Square’s local variable (Step 3). Figure 7.12 shows the method-call stack after Square’s activation record has been popped.
Fig. 7.12 | Method-call stack after method square returns to Main.
Method Main now displays the result of calling Square (Fig. 7.9, line 11). Reaching the closing right brace of Main causes its stack frame to be popped from the stack, giving Main the address it needs to return to the operating system (i.e., R1 in Fig. 7.10)—at this point, Main’s local variable x no longer exists.
You’ve now seen how valuable the stack data structure is in implementing a key mechanism that supports program execution. There’s a significant omission in the sequence of illustrations in this section. See if you can spot it before reading the next sentence. The call to the method Console.Writeln, of course, also involves the stack, which should be reflected in this section’s illustrations and discussion.