In this article, we will talk about the Stack memory in .NET.
The Stack is the part of the memory in .NET that belongs to a thread. In a simple term, each thread has its own memory that acts as a stack (last in first out).
Note. The article does not cover Heap memory, some parts are explained, but this is because there is a connection between Stack memory.
Stack Memory
The concept of a memory stack originates from computer memory architecture. And its nature touches the many programming languages. After all, a programming language is some kind of abstraction (or way of communication) on any computer machine. For this reason, many programming languages must use it.
So, even the CPU has push-and-pop instructions for providing thread (stack) execution and other things.
Well, without further confusion, let’s continue our look at .NET and their use of Stack memory.
Almost every developer knows that the name “stack” refers to a data structure. In general, the stack data structure has two techniques, one is LIFO and the other is FIFO. Our stack memory works using the LIFO (last in first out) technique. Also, do not confuse the stack data structure and stack memory. Names are the same because stack memory uses the stack data structure technique.
In .NET, there are two types of memory, one of them is a Heap and another one is a Stack and they are both located in RAM. The stack keeps track of method calls, return addresses, arguments/parameters and local variables. But Heap stores reference-type objects (this is a topic for another article).
In simple terms, each thread has its own storage in memory. This is the stack, which is the local memory for each thread. So, when threads execute methods, they declare some local variable, get the result and call another method, etc. It is for these actions that stack memory is needed.
Also, note that Stack memory is used only by one thread of execution. On the other hand, a Heap is shared memory between threads (the scope of which is declared by the OS process).
Stack Allocation
In simple terms, Stack allocation refers to the method of memory allocation that uses a region of memory known as the stack. As I mentioned before, the stack operates on a LIFO (last in first out) technique, meaning the most recently allocated memory is the first to be deallocated.
In .NET has two types. One is a value type and the other is a reference type. So, when allocating a value type in a stack, it saves the value of the type directly. But in the case of reference type which is located in heap, it saves the reference address (or pointer to the object) of the heap. In general, it means stack holds just values (reference address is some kind of value, but when needs to reach to it needs to indirect access, this is a topic for another article).
As you can see in the image above, the thread stack has 2 declared variables in a different stack frame: one is a pInst, instance of Person class and the other is a flag (int32, value type). We will not discuss the heap, but the image shows that the stack stores the reference address of pInst and the value of the flag (which is 1). In the case of the reference type, the address is the value for the stack that references the heap object (object with data and methods located in the heap).
In .NET, Stack memory has a fixed size which a 1-MB stack is allocated when a thread is created. This size is predefined once the program starts, providing a consistent amount of stack memory available for function calls and local variable storage. However, the fixed nature of the stack means that it can only hold a limited amount of data. In addition, the speed of stack memory operations is a key advantage based on the simplicity of the stack’s push and pop mechanisms.
So, almost everyone wonders why the Stack memory is fast.
- Firstly, stack memory uses static memory allocation, meaning that memory is given and taken away in a predictable way. This removes the need for complicated memory management, making operations quick and efficient.
- Secondly, as I said above the stack has a fixed size, which avoids the overhead of finding available memory space, unlike heap memory. The simple push-and-pop operations also make it faster. These operations just adjust the stack pointer, which is very fast compared to the more complex operations needed for heap memory.
- Thirdly, modern CPUs have special instructions for stack operations, like pushing and popping data, which are made to be very fast. The CPU also uses a dedicated register, the stack pointer, to efficiently manage the stack’s top position. In addition, stack memory often gets loaded into the CPU cache. This happens because stack data is frequently accessed in a straight line, making it more friendly to the cache. In contrast, accessing heap memory is indirect and can involve more cache misses, making it slower. This direct hardware support and cache efficiency ensure that stack operations have very little overhead.
Stack Frame/Call Stack
A stack frame, also known as an activation record, is a section of the stack memory allocated for a single function/method call. When a function/method is called, a new stack frame is allocated, and when the function/method returns, its stack frame is deallocated.
Basically, a stack frame contains.
- Function/Method Arguments: The values passed to the function/method.
- Local Variables: Variables that are declared inside the function/method.
- Return Address: The location in the code to return to after the function/method completes.
Let’s start the explanation with a code example.
public void Calculate()
{
int a = 1;
int b = 10;
int c = a + b;
Write(c);
Console.WriteLine("Finished");
}
public void Write(int number)
{
Console.WriteLine(number);
}
As you can see, there is a method named Calculate that declares 3 variables and is also called the Write method it.
Assume that, a thread calls the Calculate method, how would this be visualized?
As you can see from the image above, the first stack frame belongs to the Calculate method which stores 3 variables, one of which is the result of the sum of two variables. The second stack frame belongs to the Write method which stores the argument and return address (line 9 just for understanding, in fact, it contains different values). In addition, as you can see every call creates a new stack frame and each stack frame provides isolation. Therefore, it achieves local and temporary memory for each function/method.
After the Write method completes the execution, the second stack frame is deallocated and the thread returns to the first stack frame that belongs to the Calculate method, through the return address it will execute from that address line and complete the execution of the Calculate method. In this way, the call stack loop that was called in this thread will continue to execute (in our case, execution is completed).
StackOverflowException
As I mentioned before, the stack size is limited, and for this reason, when any function/method is called in an unlimited and nested way, a “StackOverflowException” occurs.
Assume that we have some function/method that is using recursion but there is not any limitation. It means the method has some calculation in which there is no return state. So, in this case, we will get “StackOverFlowException”.
Let’s take a look at a code example.
public class Program
{
public static void Main()
{
Calculate(2, 3);
}
public static void Calculate(int a, int b)
{
int c = a + b;
Calculate(c, c * c);
}
}
When you execute this code, you will get “StackOverFlowException” because recursion provides the call itself and there is no return state or limit in this function/method. With this technique, every call to the function/method creates new stack frames and when the stack limit is exceeded, we get a “StackOverFlowException”.
If you would like to learn more, stay tuned!