A common misconception among .Net developers is that the difference between value types and reference types is that value types are always stored on the stack and reference types are stored on the heap.
In fact, the compiler determines where to store variables based on the lifetime of the object. Long-lived objects are stored on the heap, and short-lived objects are likely to be stored on the stack (or in a register). As well, reference types are, of course, passed by reference and value types are passed by value (that is, copied). Eric Lippert has a great post which covers the specifics in detail http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx
How the types are actually stored in memory is an implementation detail and one which will likely never matter to most developers.
A case study on this topic I find interesting is static fields. There is one copy of a static field for all instances of a type http://msdn.microsoft.com/en-us/library/79b3xss3.aspx and their lifetime is tied to the lifetime of their domain (they are long-lived).
So, going back to the misconception, if we have a value-typed static field, how could all instances access that same variable if it was stored on the stack? I suppose that the system could copy the variable to each stack frame as it was created, but that would be inefficient – there would have to be a “master copy” of the variable stored on the heap, and quite ugly.
Instead, static fields are stored on the heap - even if they are value types.
Here is some sample code which has a class with value-typed static field, a value-typed non-static field and a value-typed local variable.
The CLR creates so-called “loader heaps” which live for the lifetime of an application domain. Static fields are stored in the one of the loader heaps called the HighFrequencyHeap. Loader heaps are not garbage collected; this is essential since static fields must have the same lifetime as the domain.
Here is some sample code which has a class with value-typed static field, a value-typed non-static field and a value-typed local variable.
class A
{
public static uint fieldA;
public uint fieldB;
}
class Program
{
static void Main(string[] args)
{
var a = new A();
a.fieldB = 2;
A.fieldA = 2;
uint localC = 2;
}
}
Let’s look under the hood at what happens during the three assignments in Main(). This is the actual assembler code from the JIT compiler as available in Visual Studio – not the CIL.
a.fieldB = 2;
00000055 mov eax,dword ptr [ebp-40h]
this moves a pointer to our non-static field on the stack to the register eax. “ebp” points to the base of the stack for this function. The stack grows downward on x86 machines (like mine), so offsets are negative.
00000058 mov dword ptr [eax+4],2
this moves the value 2 to the stack location pointed to by eax
A.fieldA = 2;
0000005f mov dword ptr ds:[00303368h],2
Now, we're going to work with our static field. This moves the value 2 to the location pointed to by ds:[00303368h]. “ds” is the data segment register which represents an offset into memory which is not part of the stack (it is, in fact, the heap). Let's examine the heap a little more closely here.
If we run the SOS debugging extension in Visual Studio and type
!eeheap -loader
in the immediate window, we get the following info about the HighFrequencyHeap:
!eeheap -loader
in the immediate window, we get the following info about the HighFrequencyHeap:
Domain 1: 00632d18
LowFrequencyHeap: 00300000(2000:2000) Size: 0x2000 (8192) bytes.
HighFrequencyHeap: 00302000(8000:2000) Size: 0x2000 (8192) bytes.
We can see that our static field (at address 00303368h - from our assembly code) is inside the boundaries of the HighFrequencyHeap (00302000h --> 00304000h). Here we can clearly see a value type that is stored on the heap!
uint localC = 2;
00000069 mov dword ptr [ebp-44h],2
this moves the value 2 to the stack location ebp-44h effectively combining the two statements in the first example into one, but the result is the same. We can also see that this is only 4 bytes away from the first example variable on the stack.
So, we see in this example that the storage location for a value type is not always the stack. In fact, there is a set of criteria the compiler uses to determine where a variable is stored.
No comments:
Post a Comment