Let's begin with example:
struct Test {
Int16 A;
Int32 B;
}
Since Int16 has length of two bytes and Int32 has length of 4 bytes one would expect total length of 6 bytes. However, upon check (with Marshal.SizeOf) we will discover that total length is 8 bytes. This difference is expected and allocated length is due to alignment of data structures.
When 32-bit processor reads data, it will do that in chunks of 4 bytes. If we want whole value to be read in one pass, it needs to be placed on alignment boundaries. If we want to read 4 bytes, we need those bytes to be at offset 0, 4, 8... or any other multiple of 4. This also explains length of first structure. First two bytes belong to variable A, then there are two padding bytes and final four bytes are variable B. Only function of those two bytes of padding is to ensure proper offset for variable B. Exact values of padding bytes are not of interest to us.
Alignment is almost always done on natural boundary of data type. 8-bit data types (e.g. Byte) will be aligned on 1-byte boundary, 16-bit data types (e.g. Int16) will be aligned on 2-byte boundary, 32-bit data types (e.g. Int32, Single, Boolean) will be aligned on 4-byte boundary and 64-bit data (e.g. Int64, Double) will be aligned on 8-byte boundary. All data types larger than that will be also aligned on 8-byte boundary.
To show it with example:
struct Test {
Int16 A;
Int16 B;
Int16 C;
}
Length of this structure will be 6. This is because every variable is on natural boundary and there is no padding needed. If we decide that B needs to be Int32, length will jump to 12. This is because byte boundary needs to be aligned on 4-byte values and we will have padding after both A and C. This also shows how important careful ordering can be. If we make C Int32, total length will be 8 - no padding.
Calculating everything by hand can be sometimes quite annoying. This is why I made this function:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
static void DebugStructureAlignment(object structure) {
var t = structure.GetType();
if (t.IsValueType) {
Debug.WriteLine("Offset Length Field");
int realTotal = 0;
foreach (var iField in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
Debug.Write(Marshal.OffsetOf(t, iField.Name).ToString().PadLeft(6));
Debug.Write(" ");
int size = Marshal.SizeOf(iField.GetValue(structure));
realTotal += size;
Debug.Write(size.ToString().PadLeft(6));
Debug.Write(" ");
Debug.WriteLine(iField.Name);
}
Debug.WriteLine(" " + Marshal.SizeOf(structure).ToString().PadLeft(6) + " bytes total");
Debug.WriteLine(" " + realTotal.ToString().PadLeft(6) + " bytes total (data without padding)");
}
}
Just give it instance of structure as parameter and you will get offsets and lengths of all fields inside of it. While this function is not perfect and I would not be surprised that there are some errors inside, mostly it will just work. In case you are playing with Windows API a lot, chances are that you have something like this already written.