When a struct is instantiated, its members are stored contiguously in memory according to their declaration order. However, this ordering significantly impacts both the total size of the struct and the performance of member access due to memory alignment.
Compilers align data members to specific addres boundaries to optimize CPU access. For example, an 8-byte double typically requires alignment to an address divisible by 8. Misaligned accesses can cause performance penalties or even hardware exceptions on some architectures.
Consider the following struct:
struct Data {
short a; // 2 bytes
// 6 padding bytes
double b; // 8 bytes
int c; // 4 bytes
// 4 padding bytes (to align next array element)
};
Here, 10 bytes of padding are added, resulting in a total size of 24 bytes. The padding between a and b ensures b starts at an 8-byte boundary. The trailing padding ensures that in an array like Data arr[100], each element remains properly aligned.
Rearrenging members from largest to smallest reduces padding:
struct Data {
double b; // 8 bytes
int c; // 4 bytes
short a; // 2 bytes
// 2 padding bytes
};
Now the struct uses only 16 bytes—saving 8 bytes per instance, or 800 bytes for an array of 100 elements.
This principle applies equally to classes. Note that classes with virtual functions include a hidden pointer to the vtable (4 bytes on 32-bit systems, 8 on 64-bit), which also affects alignment and layout.
Use sizeof to inspect the actual size, including padding. It reflects the aligned size required for arrays and proper member access.
Another subtle optimization involves instruction encoding. On many architectures (e.g., x86-64), if a member’s offset from the struct base is less than 128 bytes, the offset can be encoded as an 8-bit signed immediate in machine instructions. Offsets ≥128 require a full 32-bit encoding, increasing code size and potentially reducing instruction cache efficiency.
Example:
struct Example {
int largeArray[100]; // 400 bytes
int frequentlyUsed; // offset = 400 → needs 32-bit offset
};
Moving frequentlyUsed to the front allows it to use a compact 8-bit offset:
struct Example {
int frequentlyUsed; // offset = 0
int largeArray[100]; // offset = 4
};
Both members now have small offsets, improving code density.
Thus, best practices include:
- Group members by alignment requirement (largest first) to minimize padding.
- Place frequently accessed members within the first 128 bytes to anable compact instruction encoding.
- Put large arrays or infrequently used fields at the end.
However, aggressive reordering can reduce code readability. For instance:
// Logical grouping (readable but suboptimal)
struct Furniture {
int deskA;
double deskB;
bool deskC;
int chairA;
double chairB;
bool chairC;
};
May be reordered for efficiency:
// Optimized layout
struct Furniture {
int deskA;
int chairA;
double deskB;
double chairB;
bool deskC;
bool chairC;
// +1 byte padding (depending on alignment rules)
};
While the second version saves space and may improve performance, it sacrifices semantic grouping. The decision should balance performance needs against maintainability.