Do not use internals
The exact internal layout is subject to change. Methods like GCHandle.AddrOfPinnedObject() are designed to give you a pointer to the object's data, not to some internal stuff like headers, method table or padding bytes. So, just use those methods, do not do the math yourself.
Retrieves the address of object data in a Pinned handle.
emphasis mine
But it's interesting anyway
At the time of writing (and several years before), a .NET object in memory has the following layout:
- -pointersize: header
- 0: method table (type of the object)
- +pointersize: object data
You can see that with the following code. I have simplified the fields a bit, so that we can see patterns easier than with a float.
public struct Player
{
public int hp ;
public int maxHP;
}
class Program
{
static unsafe void Main()
{
var player = new Player();
player.hp = 0xAABB;
player.maxHP = 0xCCDD;
var boxedPlayer = (object) player;
lock (boxedPlayer)
{
Console.ReadLine(); // Put a breakpoint here
}
}
}
In 32 Bit, the memory layout (Debug/Windows/Memory) is:

64 Bit memory layout:

- dark blue: object header, containing 0x00000001 because the object is locked
- violet: method table, defining the type (
Player)
- green: data of the object,
hp here
- light blue: data of the object,
maxHp here
The copying process
If you now continue with
var ourAllocatedMemoryPTR = (byte*) Marshal.AllocHGlobal(1024);
var objHandle = GCHandle.Alloc(boxedPlayer, GCHandleType.Pinned);
var adress = objHandle.AddrOfPinnedObject();
var ptr = adress.ToPointer();
Buffer.MemoryCopy(ptr, ourAllocatedMemoryPTR, sizeof(Player), sizeof(Player));
you'll see that ptr points to 0x04a2a770, which is 0x04ECA76C + 4 (just where the data begins). sizeof(Player) is 8, which is for the two 4-byte ints.
Memory of ourAllocatedMemoryPTR before and after Buffer.MemoryCopy() :

Strange debuggers
In a debugger like WinDbg, you get the following results for 32 bit:
0:009> dd 0486a76c-4 L4
0486a768 00000001 06d68ff8 0000aabb 0000ccdd
^Address ^Header ^MT ^hp ^maxHP
0:009> ? aabb
Evaluate expression: 43707 = 0000aabb
0:009> ? ccdd
Evaluate expression: 52445 = 0000ccdd
0:009> !do 0486a76c
Name: BoxedStructInMemory.Player
MethodTable: 06d68ff8
EEClass: 06d58ee4
Size: 16(0x10) bytes
File: C:\Users\...\BoxedStructInMemory.dll
Fields:
MT Field Offset Type VT Attr Value Name
0484697c 4000001 4 System.Int32 1 instance 43707 hp
0484697c 4000002 8 System.Int32 1 instance 52445 maxHP
ThinLock owner 1 (02810B18), Recursive 0
And for 64 bit:
0:009> dq 000001a3c93ead50-8 L3
000001a3`c93ead48 00000001`00000000 00007ff8`a2f22180
000001a3`c93ead58 0000ccdd`0000aabb
0:009> !do 000001a3c93ead50
Name: BoxedStructInMemory.Player
MethodTable: 00007ff8a2f22180
EEClass: 00007ff8a2f1c5e8
Size: 24(0x18) bytes
File: C:\Users\...\BoxedStructInMemory.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ff8a2e2b1f0 4000001 8 System.Int32 1 instance 43707 hp
00007ff8a2e2b1f0 4000002 c System.Int32 1 instance 52445 maxHP
ThinLock owner 1 (000001A3C7899930), Recursive 0