C#: Stack & Heap

Grant Riordan - Jun 5 '23 - - Dev Community

Background

If you're new to C# which I'm assuming you are, following this series. Let's look at what this actually means, but first we need to understand how C# code works under the hood.

To understand this, we first must dive into memory management used across multiple OOP languages, the Heap and Stack.

What do these words mean?

The Stack is the part of memory which in essence keeps track of everything that is executed (ran) in our code, and value types.
Whereas the Heap is responsible for keep track of our objects and structures (reference types).

In C# there are variables of Value type and Reference types.

Value types are:

  • bool
  • byte
  • char
  • decimal
  • double
  • enum
  • float
  • int
  • long
  • sbyte
  • short
  • struct
  • uint
  • ulong
  • ushort

Reference types are:

  • class
  • interface
  • delegate
  • object
  • string

There is another thing involved in this process called Pointers. A Pointer is an address for the location in memory for the reference type it belongs to.

A Pointer (or Reference) and a Reference Type are distinct from one another in that a Pointer is used to retrieve a Reference Type. A pointer is a portion of memory that directs attention to another portion of memory.

Think of it as you have created a Reference type variable



Building house = new Building();


Enter fullscreen mode Exit fullscreen mode

Your house, and a Pointer is your address. The pointer is allocated to the Stack (is your address), and the house reference type is allocated to the Heap. People know how to get to the house, via the Pointer (address).

showing how values are added to the stack

The Stack:

Think of it like those child toys you used to play with in doctors surgeries, placing blocks on each spike, but you can't access the bottom block without taking the top ones off first.

The Stack works on a Last In First Out pattern. When a method is called, a new stack frame is created and pushed onto the stack. The stack frame contains space for the method's parameters, return address, local variables, and other relevant information.

As the method executes, its local variables and parameters are stored within its stack frame.

When we're done with the top box (the method is done executing) we pop it off the stack and proceed to use the data in the previous box on the top of the stack.

The Heap:

The Heap takes care of storing the Reference type variables.

The heap is more like that pile of shoes you leave at your front door for when you need to grab a pair of shoes but any will do. It's accessible at any time using your pointer because you know exactly where to look.

Golden Rules:

  1. A Reference Type always goes on the Heap

  2. Value Types always go on the Stack - Unless, they're declared within a Reference Type, then they go on the Heap.

  3. Pointers to the reference type are stored on the Stack.

  4. Global variables will be stored on the Heap as they need to be available to everything.

The Stack, as we mentioned earlier, is responsible for keeping track of where each thread is during the execution of our code (or what's been executed).

When the code executes a method, the main thread starts executing the instructions, and the code instructions live in the method tables. However, it also puts the method's parameters on the stack too. As the code runs, the variables within the methods are also pushed onto the stack.

Let's see this in an example, it might be easier:

Example 1:



public int Multiply(int value1, int value2)
{
    int result; // Stack
    result = value1 * value2;
    return result;
}


Enter fullscreen mode Exit fullscreen mode

We call the Multiply method, and as soon as we do, the method's parameters are pushed onto the stack (the method itself does not live on the stack). So currently stack would look something like this:



value2 | int
value1 | int


Enter fullscreen mode Exit fullscreen mode

The thread executes the code's instructions, and as it runs through the code, it sees the result variable. As we know local Value type variables are pushed onto the stack.



result | int
value2 | int
value1 | int


Enter fullscreen mode Exit fullscreen mode

The method finishes executing and returns the result variable, thus popping it off the stack.

All memory allocated on the stack is cleaned up.

In this example, our result variable is placed on the stack. As a matter of fact, every time a Value Type is declared within the body of a method or parameters it will end up on the Stack.

Example 2:




public void Main(string[] args){
  private int number = 0;
  Application app = new Application();
}

public class Application  {
  private int number = 42;
}


Enter fullscreen mode Exit fullscreen mode

Application Class = Reference Type

Ok, so looking at the example above where do you think the variables will be allocated to ?

Let's di-sect:

number is a local Value type variable within our program meaning it will be allocated to the Stack.

The app variable of Reference type , so it will be be allocated to the Heap and a Pointer will be allocated to the Stack to get to it. Stack.

Here's a tricky one, where will the private int number be assigned to ?

The Heap too ! As its a top level property within a Reference Type.

Let's add something a little more complex, to get the cogs working.

What about the below example:


 csharp
public void Main(string[] args){
  private int number = 0;

  Application app = new Application();
  app.SomeMethod(420);
}

public class Application  {
  private int number = 42;

  public void SomeMethod(int number){

  }
}


Enter fullscreen mode Exit fullscreen mode

Ok, so where would we expect the number argument to be assigned when the SomeMethod method is called?

Well this is where people often get confused. Yes it is inside a Reference type, however it is a method parameter so therefore goes on the Stack! As we know all method parameters of Value type and local method variables are assigned to the Stack.

Important Note:
When using Reference types, the actual variables are referring to the Pointer to the type, not each object itself.

Wait, what do I mean ? Let's take a look:

Example:




public class Application
{
    public int NumberOfThreads { get; set; }
}

public void Main (string[] args){
  Application app1 = new Application();
  Application app2 = new Application();

  app1.NumberOfThread = 10;
  Console.WriteLine(app1.NumberOfThreads) // 10

  app2 = app1;
  app2.NumberOfThreads = 40;


  Console.WriteLine(app1.NumberOfThreads); //40
}


Enter fullscreen mode Exit fullscreen mode

What ? Why has app1 Number of Threads changed, we haven't updated it.

Well when we assigned app2 to app1, it does this by Reference. It adds a pointer to the same reference type on the heap as the 1st pointer.

Therefore, when we updated the value of NumberOfThreads on app2 , we're updating the now shared reference, meaning app1 and app2 values are the same value, see image below to try and help:

Showing Stack & Heap changes with reference allocation

It can be a tough concept to grasp, but hopefully it makes sense, as it will lead on nicely to my next topic "By ref vs By value" method parameters.

As always, leave a comment if you would like anything else explained, and don't forget to subscribe, or give me a follow on twitter for updates on new content. :+

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .