C Struct Memory Alignment and Padding Explained

In the C programming language, structures frequently consume more memory than the mathematical sum of their individual members due to memory alignment and data padding. This mechanism is introduced by compilers to satisfy hardware requirements for efficient data access, where the CPU reads memory in multi-byte words rather than single bytes. This article explains how memory alignment works, the necessity of compiler-generated padding within structs, its impact on system performance, and methods for optimizing struct layouts.

What Is Memory Alignment?

Modern computer processors do not access physical memory one byte at a time. Instead, they read and write memory in chunks typically sized at 4 bytes (32-bit systems) or 8 bytes (64-bit systems), commonly referred to as memory words.

Data alignment dictates that a variable of size \(N\) bytes must have a memory address that is evenly divisible by \(N\). For instance:

  • A 1-byte char can reside at any byte boundary (divisible by 1).
  • A 2-byte short must reside at an even address (divisible by 2).
  • A 4-byte int or float must reside at an address divisible by 4.
  • An 8-byte double or pointer must reside at an address divisible by 8.

The Purpose of Struct Padding

When members of different types are declared inside a struct, placing them sequentially without gaps can cause variables to fall on misaligned addresses. To prevent this, the compiler automatically inserts unused, empty bytes called padding between members and at the end of the struct.

Padding serves two primary functions inside structs:

  1. Internal Member Alignment: It ensures that every member variable begins at a memory address that aligns with its natural type boundary.
  2. Trailing Struct Alignment: It rounds the total size of the structure up to a multiple of its largest member's alignment requirement. This ensures that every element remains properly aligned when declaring an array of that struct type.

Why Memory Alignment and Padding Matter

The enforcement of memory alignment and padding provides essential system-level advantages:

  • Processing Speed: When data is aligned, the CPU can fetch it using a single memory access cycle. If a 4-byte integer crosses a 4-byte boundary (misaligned), the CPU must execute two separate memory read cycles, mask the unneeded bits, and merge the remaining bytes together.
  • Hardware Architecture Constraints: While x86 processors can tolerate misaligned access at the expense of performance, certain architectures (such as older ARM, MIPS, and SPARC platforms) generate a hardware exception or bus error when encountering an unaligned memory read or write.
  • Cache Line Efficiency: Proper alignment prevents single variables from crossing CPU cache line boundaries, which minimizes cache misses and maximizes cache throughput.

Memory Alignment in Practice

Consider the following struct layout:

struct BadLayout {
    char a;      // 1 byte
    int b;       // 4 bytes
    char c;      // 1 byte
};

One might expect sizeof(struct BadLayout) to be 6 bytes (\(1 + 4 + 1\)). In reality, on a standard 32-bit or 64-bit system, the compiler produces an allocation of 12 bytes:

  • char a occupies byte 0.
  • 3 bytes of padding are inserted so b starts at offset 4 (a multiple of 4).
  • int b occupies bytes 4 through 7.
  • char c occupies byte 8.
  • 3 bytes of trailing padding are added so the entire struct size becomes 12 bytes, which is divisible by the largest alignment constraint (4 bytes for int).

Optimizing Struct Memory

Developers can eliminate redundant padding without altering struct functionality simply by reordering members from largest to smallest:

struct OptimizedLayout {
    int b;       // 4 bytes
    char a;      // 1 byte
    char c;      // 1 byte
};               // 2 bytes of trailing padding added

By placing the 4-byte int first, char a and char c pack into the next two bytes (bytes 4 and 5). The compiler only adds 2 bytes of trailing padding to round the struct up to 8 bytes, saving 33% of the memory used by struct BadLayout.

While compilers often provide directives like #pragma pack(1) or __attribute__((packed)) to force structs to zero padding, doing so induces severe performance penalties and risks alignment faults on stricter architectures. Ordering struct members manually remains the standard and most efficient way to balance memory footprint and hardware speed.