Stack vs Heap memory in C#

In this post I’ll explain the Stack vs Heap in C# differences. As you may know in C# there are two types of memory – Stack and Heap, so let’s look at each of them in detail.

Static memory (Stack)

Let’s start with the stack firstly. At first it is a pre-allocated memory for the program, where are kept its value type variables, call stack, and metadata.

Secondly, it is a LIFO (Last in first out) data structure and data can be deleted only from the top. The value types in C# are inherited from System.ValueType and are as follows:

C# value types
Stack vs Heap in C# – value types

All of that data is persisted in the stack.

Dynamic memory (Heap)

The second type of memory is dynamic memory, also called heap. Everything outside the stack is kept in the dynamic memory. The main difference compared to the stack is that the heap is hierarchical memory. The objects can be added or deleted in any order.

It is called dynamic because every time when our program needs additional memory, it calls to the OS, and the OS provides an additional heap. In the heap are kept the so-called reference types, which inherit from the System.Object class.

Classes on C# are reference types, and each specific instance of a class is kept in the heap. In the static memory, the program keeps only an address for that object, which points to its specific place in the heap:

Stack memory address to actual value in the heap
Stack vs Heap in C# – Stack memory address to actual value in the heap

For instance, in the image above, jane’s instanceis kept in the heap under the address17457andmike’s instance – is under45616. If the reference type is null, like with thejohnDoevariable, its address in the stack will be0. In other words – it doesnโ€™t have a heap address – zero is not pointing anywhere in the heap. Thatโ€™s why every time you try to access a property of such an object you getNullReferenceException. The program is trying to find the heap address of that object, but it actually doesnโ€™t exist.

Get object’s Stack address example

Letโ€™s look at the following example:

C#
internal class Program
    {
        static void Main(string[] args)
        {
            int number = 1;
            var address = GetObjectMemoryAddress(number);
            Console.WriteLine($"Stack address of the reference type {typeof(int).Name}: {address}");

            Point notDefinedPoint = null;
            address = GetObjectMemoryAddress(notDefinedPoint);
            Console.WriteLine($"Stack address of the reference type {typeof(Point).Name}: {address}");

            Point definedPoint = new Point();
            address = GetObjectMemoryAddress(definedPoint);
            Console.WriteLine($"Stack address of the reference type {typeof(int).Name}: {address}");
        }

        private static IntPtr GetObjectMemoryAddress(object obj)
        {
            var memoryLocator = GCHandle.Alloc(obj, GCHandleType.Pinned);
            var stackAddress = memoryLocator.AddrOfPinnedObject();
            memoryLocator.Free();
            return stackAddress;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class Point
    {
    }

The output of this program will be:

Reference types addresses demo
Reference types addresses demo

As you can see the object Point assigned with the null values, doesnโ€™t have an address in the stack โ€“ it is just 0.

The GetObjectMemoryAddress method is using the System.Runtime.InteropServices.GCHandle object, which gives us the ability to pass it a managed object, and access its address from the unmanaged memory. The method is doing the following:

  1. Calling GCHandle.Alloc, with the object as a first parameter, actually creates a handle for working with that object in the unmanaged memory. By passing GCHandleType.Pinned, as a second parameter, we are saying to the garbage collector, not to change this objectโ€™s address.
  2. AddrOfPinnedObject is returning the object address from the stack.
  3. As you can suggest it is not a good idea to interfere with the garbage collector as we do here with the GCHandleType.Pinned parameter. So, make sure to call the Free() method of the handle to release it.

Additional considerations

Another important thing in the example is the [StructLayout(LayoutKind.Sequential)] attribute we use to mark the reference type Point. Without this you will get the following exception:

The CLR is trying to keep us safe. We may think our object is pinned, but actually, it is not. An object isnโ€™t pinned as long as it has properties that are not pinned. We donโ€™t have properties in our example, but the CLR is taking care anyway.

The LayoutKind default value is Auto and means that the CLR decides how to keep the members of the Point object in the unmanaged memory. But we can only pin them if they are stored sequentially. So, using LayoutKind.Sequential will not throw an exception, and you can run the example. You can also download it from my repo and try different approaches.