Introduction
In C, all the variables tend to have a particular liking about some specific memory addresses. These memory addresses are called their natural alignment or natural boundaries. The tendency of aligning of data types to their unique natural alignments is called Memory Alignment in C.
Example
Let us understand the working of memory with an example of structs.
Note: The working of memory stays the same without structs. An example of struct is taken just for simplicity of understanding.
struct Example {
char a; // 1 byte
int b; // 4 bytes
};How the struct is laid out in memory:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| a | padding | padding | padding | b | b | b | b |
Why Align Data?
You must be thinking, it would be a waste of space if we keep aligning all the data to their particular boundaries. At the end, we sure waste the extra space by padding in between two data types as we saw in structs. But at the cost of space we are making memory access easier.
At the hardware level, memory is accessed in chunks called words, typically 2, 4, 8, or 16 bytes wide depending on the architecture.
Say you have a 32-bit (4-byte word) CPU:
- The CPU has a data bus that’s 4 bytes wide.
- So each read/write from/to RAM ideally happens in 4-byte units aligned on 4-byte boundaries.
Here is an example of what would happen if the data is not aligned
struct Example {
char a; // 1 byte
int b; // 4 bytes
}__attribute((packed)); // Removes all padding between typesHow the struct is laid out in memory:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| a | b | b | b | b | - | - | - |
The char and integer now cover 5 bytes of space in memory. But, we know that the process can access only in chunks. A 32-bit (4 byte) processor would need 2 cycles to access a single integer variable b.
- First cycle to read bytes 1-3
- Second cycle to read byte 4
After this, it needs to combine the seperate results internally to get the final integer. This breaks the fast, single-cycle access assumption.
Natural Alignments of Data Types
Alignment requirements depend on the data type and the architecture (e.g., 32-bit, 64-bit). On most platforms, the alignment of a data type is equal to its size, though not always.
Rule of Thumb
A data type’s natural boundary is any address which is divisible by n, where n is the size of the data type.
Here’s a general table for common types:
| Data Type | Size (bytes) | Alignment (bytes) | Notes |
|---|---|---|---|
char | 1 | 1 | No alignment needed |
short | 2 | 2 | Aligned on 2-byte boundary |
int | 4 | 4 | Aligned on 4-byte boundary |
float | 4 | 4 | Same as int |
long | 4 or 8 | 4 or 8 | Depends on architecture |
long long | 8 | 8 | For 64-bit integers |
double | 8 | 8 | On most systems |
long double | 8, 12, or 16 | 8, 16 | Varies by compiler/ABI |
void* | 4 (x86), 8 (x64) | 4 or 8 | Pointer size matches architecture |
struct | Depends | Largest member alignment | Might have padding |
For more data types, refer Data Types