(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).