Most of this is purely an implementation detail and could change with the next version of the CLR.
Run the following program as x86 or x64 and you can empirically determine the size of your structure:
struct Point { int x; int y; }
class Program
{
const int Size = 100000;
private static void Main(string[] args)
{
object[] array = new object[Size];
long initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < Size; i++)
{
array[i] = new Point[3];
}
long finalMemory = GC.GetTotalMemory(true);
GC.KeepAlive(array);
long total = finalMemory - initialMemory;
Console.WriteLine("Size of each element: {0:0.000} bytes",
((double)total) / Size);
}
}
The code is pretty simple but was shamelessly stolen from Jon Skeet.
If you do run this you will get the following results:
x86: 36 byte
x64: 48 byte
In the current implementation the size of every object is aligned to the pointer size, this means every object in x86 is 4 byte aligned, and under x64 8 byte (this is absolutely possible to change - HotSpot in Java for example aligns everything to 8 byte even under x86).
Arrays in C# are somewhat special with their length: While they do have a 4 byte length field, under x64 they also include 4 byte additional padding (vm/object.h:766 contains the interesting part). This is most likely done to guarantee that the start of the actual fields is always 8 byte aligned under x64 which is required to get good performance when accessing longs/doubles/pointers (the alternative would be to only add the padding for these types and specialize the length computation - unlikely to be worth the additional complexity).
On x86 the object header is 8 byte and the array overhead 4 byte, which gives us 36 byte.
On x64 the object header is 16 byte and the array overhead 8 byte. This gives us 24 + 24 = 48 byte.
For anyone wanting actual proof instead of empirical tests about the header size and alignment you can just go to the actual source: Here is the object definition of the coreclr. Check out the comment starting at line 178 which states:
// The only fields mandated by all objects are
//
// * a pointer to the code:MethodTable at offset 0
// * a poiner to a code:ObjHeader at a negative offset. This is often zero. It holds information that
// any addition information that we might need to attach to arbitrary objects.
You can also look at the actual code to see that those pointers are actual pointers and not DWORDs or anything else.
The code for aligning the object sizes is also in the same file:
#define PTRALIGNCONST (DATA_ALIGNMENT-1)
#ifndef PtrAlign
#define PtrAlign(size) \
((size + PTRALIGNCONST) & (~PTRALIGNCONST))
#endif //!PtrAlign
DATA_ALIGNMENT
is defined as 4 for x86 (vm/i386/cgencpu.h) and ARM (vm/arm/cgencpu.h) and 8 for x64 (vm/amd64/cgencpu.h). The code itself is nothing but a standard optimized "round to next multiple of DATA_ALIGNMENT
" assuming data alignment is a power of 2 method.