Question

public class MyClass
{
    public int Age;
    public int ID;
}

public void MyMethod() 
{
    MyClass m = new MyClass();
    int newID;
}

To my understanding, the following is true:

  1. The reference m lives on the stack and goes out of scope when MyMethod() exits.
  2. The value type newID lives on the stack and goes out of scope when MyMethod() exits.
  3. The object created by the new operator lives in the heap and becomes reclaimable by the GC when MyMethod() exits, assuming no other reference to the object exists.

Here is my question:

  1. Do value types within objects live on the stack or the heap?
  2. Is boxing/unboxing value types in an object a concern?
  3. Are there any detailed, yet understandable, resources on this topic?

Logically, I'd think value types inside classes would be in the heap, but I'm not sure if they have to be boxed to get there.

Edit:

Suggested reading for this topic:

  1. CLR Via C# by Jeffrey Richter
  2. Essential .NET by Don Box
Was it helpful?

Solution

Value-type values for a class have to live together with the object instance in the managed heap. The thread's stack for a method only lives for the duration of a method; how can the value persist if it only exists within that stack?

A class' object size in the managed heap is the sum of its value-type fields, reference-type pointers, and additional CLR overhead variables like the Sync block index. When one assigns a value to an object's value-type field, the CLR copies the value to the space allocated within the object for that particluar field.

Take for example, a simple class with a single field.

public class EmbeddedValues
{
  public int NumberField;
}

And with it, a simple testing class.

public class EmbeddedTest
{
  public void TestEmbeddedValues()
  {
    EmbeddedValues valueContainer = new EmbeddedValues();

    valueContainer.NumberField = 20;
    int publicField = valueContainer.NumberField;
  }
}

If you use the MSIL Disassembler provided by the .NET Framework SDK to peek at the IL code for EmbeddedTest.TestEmbeddedValues()

.method public hidebysig instance void  TestEmbeddedValues() cil managed
{
  // Code size       23 (0x17)
  .maxstack  2
  .locals init ([0] class soapextensions.EmbeddedValues valueContainer,
           [1] int32 publicField)
  IL_0000:  nop
  IL_0001:  newobj     instance void soapextensions.EmbeddedValues::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.s   20
  IL_000a:  stfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_000f:  ldloc.0
  IL_0010:  ldfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_0015:  stloc.1
  IL_0016:  ret
} // end of method EmbeddedTest::TestEmbeddedValues

Notice the CLR is being told to stfld the loaded value of "20" in the stack to the loaded EmbeddValues' NumberField field location, directly into the managed heap. Similarly, when retrieving the value, it uses ldfld instruction to directly copy the value out of that managed heap location into the thread stack. No box/unboxing happens with these types of operations.

OTHER TIPS

  1. Any references or value types that an object own live in the heap.
  2. Only if you're casting ints to Objects.

The best resource I've seen for this is the book CLR via C# by Jeffrey Richter. It's well worth reading if you do any .NET development. Based on that text, my understanding is that the value types within a reference type do live in the heap embedded in the parent object. Reference types are always on the heap. Boxing and unboxing are not symmetric. Boxing can be a bigger concern than unboxing. Boxing will require copying the contents of the value type from the stack to the heap. Depending on how frequently this happens to you there may be no point in having a struct instead of a class. If you have some performance critical code and you're not sure if boxing and unboxing is happening use a tool to examine the IL code of your method. You'll see the words box and unbox in the IL. Personally, I would measure the performance of my code and only then see if this is a candidate for worry. In your case I don't think this will be such a critical issue. You are not going to have to copy from the stack to the heap (box) every time you access this value type inside the reference type. That scenario is where boxing becomes a more meaningful problem.

  • Ans#1: Heap. Paraphrasing Don Box from his excellent 'Essential .Net Vol 1'

Reference Types(RT) always yield instances that are allocated on the heap. In contrast, value types(VT) are dependent on the context - If a local var is a VT, the CLR allocates memory on the stack. If a field in a class is a member of a VT, then the CLR allocates memory for the instance as part of the layout of the object/Type in which field is declared.

  • Ans#2: No. Boxing would occur only when you access a struct via a Object Reference / Interface Pointer. obInstance.VT_typedfield will not box.

    RT variables contains the address of the object it refers to. 2 RT var can point to the same object. In contrast, VT variables are the instances themselves. 2 VT var cannot point to same object(struct)

  • Ans#3: Don Box's Essential .net / Jeffrey Richter's CLR via C#. I have a copy of the former... though the later may be more updated for .Net revisions

Do value types within objects live on the stack or the heap?

On the heap. They are part of the allocation of the footprint of the object, just like the pointers to hold references would be.

Is boxing/unboxing value types in an object a concern?

There's no boxing here.

Are there any detailed, yet understandable, resources on this topic?

+1 vote for Richter's book.

A variable or other storage location of a structure type is an aggregation of that type's public and private instance fields. Given

struct Foo {public int x,y; int z;}

a declaration Foo bar; will cause bar.x, bar.y, and bar.z to be stored wherever bar is going to be stored. Adding such a declaration of bar to a class will, from a storage-layout perspective, be equivalent to adding three int fields. Indeed, if one never did anything with bar except access its fields, the fields of bar would behave the same as would three fields bar_x, bar_y, and bar_cantaccessthis_z [accessing the last one would require doing things with bar other than accessing its fields, but it would take up space whether or not it's ever actually used for anything].

Recognizing structure-type storage locations as being aggregations of fields is the first step to understanding structures. Trying to view them as holding some kind of object might seem "simpler", but doesn't match how they actually work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top