Why structs have holes
Every scalar type has an alignment: on most ABIs a 4-byte int must sit at an address divisible by 4, an 8-byte double or pointer at one divisible by 8. The compiler inserts padding before a member to reach its alignment, and at the end so that arrays of the struct keep every element aligned (the struct’s own alignment is that of its most-aligned member). struct { char c; int i; } is therefore 8 bytes, not 5, and struct { char a; long b; char c; } is 24 bytes while { long b; char a; char c; } is 16. Ordering members from the largest alignment to the smallest removes most padding, which is what the reorder suggestion does.
What differs between ABIs
| Type | x86-64 SysV | AArch64 | i386 | ARM32 | MSVC x64 |
|---|---|---|---|---|---|
| long | 8 | 8 | 4 | 4 | 4 |
| pointer | 8 | 8 | 4 | 4 | 8 |
| double | 8, align 8 | 8, align 8 | 8, align 4 | 8, align 8 | 8, align 8 |
| long long | align 8 | align 8 | align 4 | align 8 | align 8 |
| long double | 16, align 16 | 16, align 16 | 12, align 4 | 8 | 8 |
| wchar_t | 4 | 4 | 4 | 4 | 2 |
| bit-field rules | Itanium: a field is placed at the next bit unless it would cross a window of sizeof(type) bytes; unnamed fields do not affect alignment (x86) or do (ARM) | each run of same-sized fields gets its own storage unit; a type change starts a new unit | |||
The same source can therefore produce different binary layouts on different targets, which is why structs that go over the network or into files should use fixed-width types, explicit padding members and static_assert(sizeof(struct x) == 24).
Bit-fields
Adjacent bit-fields are packed into the storage unit of their declared type. With GCC and Clang a field goes at the next free bit unless it would straddle a sizeof(type)-byte window, in which case it moves to the next alignment boundary; a zero-width field (int :0) forces the next field to a new unit. MSVC allocates a fresh unit whenever the type size changes, so int a:8; char b:8; takes 5 bytes on MSVC and 2 on GCC. Bit order inside the unit follows the target’s endianness (least significant bits first on little-endian), which is why bit-fields are a poor way to describe wire formats.
packed, aligned and #pragma pack
__attribute__((packed)) removes all padding (members may be misaligned, which is slow on x86 and a fault on some ARM configurations). #pragma pack(n) caps member alignment at n bytes. __attribute__((aligned(n))) and _Alignas(n) raise alignment, typically to 16 for SIMD or 64 for a cache line. All of these are shown in the layout.
Questions
How accurate is this?
The engine is tested against clang -fdump-record-layouts for x86-64, i386, AArch64, ARM32 and MSVC x64 on a set of structs with bit-fields, packing, nested unions and zero-width fields. GCC and Clang agree with each other on these ABIs. For exotic cases (over-aligned types in bit-fields, __int128 on 32-bit, vector types) check with the compiler.
How do I get the real values from my compiler?
clang -Xclang -fdump-record-layouts -c file.c prints every struct; GCC: compile with -g and run pahole on the object, or use offsetof() and sizeof() in a small program. Add static_assert(offsetof(struct s, x) == 8) to lock a layout in.
It says a type is unknown.
Types from system headers (size_t, uint32_t, time_t and the Windows DWORD family) are built in; anything else needs its typedef pasted above the struct that uses it. An unknown name is treated as a 4-byte int and flagged.
Can it decode a struct from a core dump or packet?
Paste the bytes in the decode section. Integers, floats, chars, pointers, char arrays and bit-fields are shown with the byte order you choose; nested structs are listed but not expanded (select the inner struct to decode it separately).
Why does reordering not help my struct?
If members are already sorted by alignment, or the struct has bit-fields or anonymous unions (not reordered automatically), the suggestion is hidden. Tail padding is unavoidable when the largest member’s alignment does not divide the sum of sizes.