Memory in .NET - what goes where

A lot of confusion has been wrought by people explaining the difference between value types and reference types as "value types go on the stack, reference types go on the heap". This is simply untrue (as stated) and this article attempts to clarify matters somewhat.

What's in a variable?

The key to understanding the way memory works in .NET is to understand what a variable is, and what its value is. At the most basic level, a variable is just an association between a name (used in the program's source code) and a slot of memory. A variable has a value, which is the contents of the memory slot it's associated with. The size of that slot, and the interpretation of the value, depends on the type of the variable - and this is where the difference between value types and reference types comes in.

The value of a reference type variable is always either a reference or null. If it's a reference, it must be a reference to an object which is compatible with the type of the variable. For instance, a variable declared as Stream s will always have a value which is either null or a reference to an instance of the Stream class. (Note that an instance of a subclass of Stream, eg FileStream, is also an instance of Stream.) The slot of memory associated with the variable is just the size of a reference, however big the actual object it refers to might be. (On the 32-bit version of .NET, for instance, a reference type variable's slot is always just 4 bytes.)

The value of a value type is always the data for an instance of the type itself. For instance, suppose we have a struct declared as:

struct PairOfInts
{
    public int a;
    public int b;
}

The value of a variable declared as PairOfInts pair is the pair of integers itself, not a reference to a pair of integers. The slot of memory is large enough to contain both integers (so it must be 8 bytes). Note that a value type variable can never have a value of null - it wouldn't make any sense, as null is a reference type concept, meaning "the value of this reference type variable isn't a reference to any object at all".

So where are things stored?

The memory slot for a variable is stored on either the stack or the heap. It depends on the context in which it is declared:

There are a couple of exceptions to the above rules - captured variables (used in anonymous methods and lambda expressions) are local in terms of the C# code, but end up being compiled into instance variables in a type associated with the delegate created by the anonymous method. The same goes for local variables in an iterator block.

A worked example

The above may all sound a bit complicated, but a full example should make things a bit clearer. Here's a short program which does nothing useful, but should demonstrate the points raised above.

using System;

struct PairOfInts
{
    static int counter=0;
    
    public int a;
    public int b;
    
    internal PairOfInts (int x, int y)
    {
        a=x;
        b=y;
        counter++;
    }
}

class Test
{
    PairOfInts pair;
    string name;
    
    Test (PairOfInts p, string s, int x)
    {
        pair = p;
        name = s;
        pair.a += x;
    }
    
    static void Main()
    {
        PairOfInts z = new PairOfInts (1, 2);
        Test t1 = new Test(z, "first", 1);
        Test t2 = new Test(z, "second", 2);
        Test t3 = null;
        Test t4 = t1;
        // XXX
    }
}

Let's look at what's where in memory at the line marked with the comment "XXX". (Assume that nothing is being garbage collected.)


Back to the main page.