8

A naïve type system would store objects as a pointer to its type (which contains lots of useful information, like a vtable, object size, etc.) followed by its data. If .Net had such a type system an object would take up 4 bytes on a 32-bit system, and 8 bytes on 64-bit.

We can see that it doesn't. The object overhead is two pointer-sizes, plus, there's a 'minimum' size of one more pointer-size.

So what does object actually store in it, behind the scenes?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
configurator
  • 40,828
  • 14
  • 81
  • 115

2 Answers2

7

Yes, that's what it looks like. The 'type handle', aka 'method table pointer' is at offset 0, the object data follows at offset 4. There's an extra field at offset-4 named the 'syncblock'. It is there because it also participates in the garbage collected heap when the object space is not in use, a double-linked list of free blocks that requires two pointers. Not letting that going to waste, the syncblock has several uses like storing the lock state, storing the hash code, storing a pointer to an explicit syncblock when too much needs to be stored.

The smallest possible object is for a boxed byte, 4 + 4 + 1 = 9 bytes. But allocation granularity for the GC heap is 4 bytes so you'll get the next multiple of 4, 12 bytes.

This is all pretty visible with the debugger in Visual Studio. You'll find hints in this answer.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
5

(This is all from the Microsoft Shared Source CLI; it has the source code of the CLR.)

If you take a look at clr\src\vm\object.h, you will see:

// The generational GC requires that every object be at least 12 bytes in size.
#define MIN_OBJECT_SIZE     (2*sizeof(BYTE*) + sizeof(ObjHeader))

which is pretty self-explanatory. Furthermore, in clr\src\vm\gcscan.cpp, you can see statements such as

_ASSERTE(g_pObjectClass->GetBaseSize() == MIN_OBJECT_SIZE);

or

_ASSERTE(totalSize < totalSize + MIN_OBJECT_SIZE);

which I think explains why you're seeing the unexpected object sizes. :)


Update:

@Hans had a great point on the sync block; I just want to point out a subtlety, documented again in object.h:

/* Object
 *
 * This is the underlying base on which objects are built. The MethodTable
 * pointer and the sync block index live here. The sync block index is actually
 * at a negative offset to the instance. See syncblk.h for details.
 */

class Object
{
    protected:
        MethodTable*    m_pMethTab;
    //No other fields shown here!
};

Notice this part:

The sync block index is actually at a negative offset to the instance.

So the sync block apparently doesn't actually follow the method table (as Hans mentioned), but it comes before it -- so it's not a "normal" part of the object (for the lack of a better word).

user541686
  • 205,094
  • 128
  • 528
  • 886