Foundations · Module 4 · Lesson 2 of 9
Struct Layout, Padding and Alignment
See why a struct is often larger than the sum of its members, how alignment rules place each member and pad the end, and how reordering members and checking layouts saves memory and avoids file-format traps.
By the end you can
- Compute member offsets and the total size of a struct from the alignment rules
- Explain why padding exists, both between members and at the end
- Reorder members to reduce padding
- Explain why writing a struct's raw bytes to a file is unsafe
Builds on Structs: Grouping Related Data.
01 · A question first
The three members of this struct add up to 6 bytes, yet sizeof says 12. Where did the other six go, who put them there, and can you take them back by doing nothing more than reordering the lines?
Make a guess, then step through the trace below to check it.
02 · Watch it run
struct A { char c; int i; char d; };struct B { int i; char c; char d; };A a{'A', 1, 'B'};B b{1, 'A', 'B'};- byte 0--
- byte 1--
- byte 2--
- byte 3--
- byte 4--
- byte 5--
- byte 6--
- byte 7--
- byte 8--
- byte 9--
- byte 10--
- byte 11--
Struct A holds a char, an int and another char: 1 + 4 + 1 is 6 bytes of data. The twelve cells are the bytes that one A will occupy. The compiler has already decided the layout from the alignment rules: an int must start at an address that is a multiple of 4.
Machine work: compile time: place c at 0, i at 4 and d at 8; round the size up to 12
03 · The idea
Start with the problem
Lesson 1 mentioned that the size of a struct is not the sum of its members. Take a struct with a char, an int and another char. The data is 1 + 4 + 1, which is 6 bytes, yet sizeof reports 12. Half of the object seems to be missing from the accounting.
It matters because the waste multiplies. A million of these structs cost 12 MB instead of 8 MB for exactly the same information, and the extra bytes have to travel through the cache too. To see where the bytes go, you need the idea of alignment.
How the idea works
Alignment. Every type has an alignment: its objects must start at an address that is a multiple of a certain number. For the built-in types the alignment normally equals the size: 1 for char, 2 for short, 4 for int, and 8 for double and pointers. The operator alignof(T) reports it. The reason is the hardware. Memory and caches move data in aligned blocks, so a misaligned access may straddle two cache lines or two pages and cost extra, and some processors refuse it altogether. The compiler therefore guarantees that every object is aligned.
Padding. To keep each member aligned, the compiler inserts unused bytes called padding. Two rules decide where:
- Each member is placed at the next offset that is a multiple of its own alignment.
- The total size of the struct is rounded up to a multiple of the struct's alignment, which is the largest alignment among its members.
The trace applies them to a struct A. After the char c at offset 0, the next free offset is 1, but the int i needs a multiple of 4, so 3 bytes of padding follow (step 2). The int lands at offset 4, and the second char at offset 8. Rule 2 then pads the tail from 9 to 12 bytes (step 4). The reason for the tail is arrays: element number k of an array starts at k * sizeof(A), so if the size were 9, the int of the second element would be misaligned.
Reordering. The compiler may not reorder members. They are stored in the order you declare them, so you control the padding. Declaring members from the largest alignment to the smallest usually leaves the least:
struct Bad { char a; double b; char c; }; // 24 bytes
struct Good { double b; char a; char c; }; // 16 bytesThe second struct B in the trace shows the same effect for the first example: the identical data takes 8 bytes instead of 12 (step 5).
Inspecting. The operators sizeof and alignof report the size and the alignment, and the macro offsetof(Type, member), from the header <cstddef>, reports where a member lives. The warning -Wpadded in GCC and Clang lists every padding hole, and the tool pahole prints a struct's layout from the debug information. A line such as static_assert(sizeof(A) == 12); pins a layout in your code and fails the build if it ever changes.
Packing. The directive #pragma pack and the attribute __attribute__((packed)) remove the padding, at the price of misaligned members, which are slower and on some processors unsafe. They are mostly used to match an external format, which is a reason for the caution in the next paragraphs.
Padding holds garbage. The contents of padding bytes are unspecified. Comparing two structs with memcmp, or writing a struct's bytes straight to a file, includes that garbage, and can even leak stale data that was once in memory.
Why it is correct
Alignment rules come from the hardware: load and store units and caches work efficiently on naturally aligned chunks. Each platform's ABI fixes the size and alignment of every type, and the compiler enforces them with padding. Because the rules are the same across compilers for a platform, libraries built separately can agree on the layout of a shared struct.
Rule 2 is what keeps arrays correct: if element 0 is aligned, then element k at k * sizeof(T) is aligned too, since the size is a multiple of the alignment. And since C++ stores members in declaration order, the layout is predictable and under your control. Some other languages reorder members for you, and that choice trades control for convenience.
What it costs
Memory. In the example, A takes 50 percent more space than B for the same data. A million objects waste 4 MB.
Cache. A cache line holds 64 bytes, which is 8 objects of 8 bytes but only 5 whole objects of 12 bytes. Scanning a big array of the smaller struct touches about a third fewer cache lines, which often shows up directly in run time.
Misaligned access. On x86 it usually costs a little unless it crosses a cache line, where it costs more. Some other architectures trap. Atomic operations require alignment.
Readability. Ordering members by size can separate fields that belong together. Do it for structs that exist in large numbers, and keep related fields together for the rest, with a comment where the order matters.
Pitfalls and variations
Adding up members. sizeof is not the sum of the member sizes, and code that assumes it breaks.
Raw bytes to files and networks. The padding, the sizes of the types, the alignment and the byte order, as lesson 3 of module 1 showed, can all differ between compilers and machines. Write the members one at a time, in a defined order and width. Lesson 7 of this module returns to this.
Comparing with memcmp. Two structs with equal members can differ in their padding bytes, so a byte comparison can say they are different.
Casting a buffer to a struct pointer. The buffer may be misaligned, and the cast also breaks the aliasing rules, which makes it undefined behavior. Copy the bytes into the struct with std::memcpy instead.
Layout changes break the ABI. Reordering or adding members changes the sizes and offsets, so every piece of code that uses the struct must be recompiled.
Variations. The specifier alignas(N) increases the alignment of a type or variable, for example to put hot data on its own cache line. An empty struct still has size 1, so that distinct objects have distinct addresses, and C++20's [[no_unique_address]] lets an empty member take no space. Bit-fields pack several small members into one integer, but their layout is up to the implementation.
Where it is used
Game engines tune struct layouts to cache lines for speed. Embedded code maps structs onto hardware registers. Network and file formats define exact layouts, which code reads and writes member by member. Library authors keep layouts fixed to stay compatible with code that has already been compiled.
The next lesson, "Passing, Returning and Pointing to Structs", brings the layout and the copying rules to the boundary between functions, and introduces the arrow operator for reaching members through a pointer.
04 · Prove it
Question 1 of 4 · answer from memory
With 4-byte ints and 1-byte chars, what is `sizeof` of `struct S { char a; int b; char c; };`?
05 · Practice
Asked in interviews
Something wrong with this lesson? Report a problem