Class Size, Alignment and Padding
Stop defining/declaring member variables in random order.
1. Size
The size of a C++ class is primarily determined by its non-static data members, plus any additional memory aligment and padding requirements.
Each fundamental types (e.g., char
, float
, int
) has a fixed size and alignment. Smaller types can cause padding if places inefficiently.
1 | struct A { |
A pointer is just an address, it takes 8 bytes
on a 64-bit system, and 4 bytes
on a 32 bit system.
1 | struct A { |
Static members do not affect class size because they are stored separately in global memory.
1 | struct A { |
When a class contains another class object as a member, it inherits the memory layout of the conatined object.
1 | struct A { |
If a class has at least one virtual function, it will contain a hidden virtual table pointer. The vptr
takes 8 bytes
on a 64-bit system.
1 | struct C { |
Empty base class optimization means an empty base class usually takes 1 byte
to ensure unique address, however, compilers can optimize it by reducing its actual size to 0 byte
.
1 | struct Empty {}; |
STL containers
take spaces dependent on their implementations. For example, std::vector<T>
typically uses 3 pointers to manage its dynamic array.
1 | // std::vector<T structure |
1 | struct A { |
Lambda functions in C++ are implemented as unnamed (closure) classes. When a lambda is declared inside a class, it is treated as a hidden member class that can potentially increase the class size depending on how the lambda captures variables. Its use cases are a bit complicated, we will use another post to discuss it in the future.
2. Alignment and Padding
Alignment is a fundamental concept in C++ that ensures efficient memory access and prevents performance penalties due to unaligned memory access.
Each data type in C++ has an alignment requirement, which dictates that its memory address must be a multiple of a certain number (power of 2). The compiler inserts padding bytes to satisfy these requirements.
The key rules:
- The alignment requirement of a struct/class is determined by its largest member.
- The total size of a struct/class must be a multiple of its largest alignment.
1 | struct A { |
If we simply switch the order, we can optimize the memory layout.
1 | struct A { |
In class inheritance, the alignment rule is also inherited from base class.
1 | struct Base { |
We can check the alignment with alignas
.
1 | struct A { |
alignas
is powerful, and it allows us to control the alignment. alignas(16)
forces the struct/class to align to 16 bytes
.
1 | struct alignas(16) A { |
We can also use alignas
to have fine-grain control over each individual member.
1 | struct A { |
We added another 4
padding at the end because the total size must be the multiple of its largest member, in our case, it would be alignas(8) int b;
.
alignas
can be useful for SIMD optimization.
We can also use the #pragma pack(1)
directive controls structure packing, telling the compiler to minimize padding between members by aligning them to 1-byte
boundaries instead of their natural alignment. It is compiler-specific, and its behavior can vary across different compilers.
1 |
|
1 |
|
3. Conclusion
- Alignment and padding play a crucial role in C++ by optimizing memory access performance.
- Efficient memory layout can be achieved by strategically reordering member declarations.
- While we have manual control over alignment and padding, it should be used carefully to prevent unnecessary memory overhead or performance degradation.
- In large projects, this aspect is often overlooked. However, when a class or struct is instantiated hundreds or thousands of times, careful design becomes essential.
Class Size, Alignment and Padding