Alignment and Padding
Introduction
Every now and then, every C developer needs to calculate the size of modeled data. For example, you may wonder how your system will scale or what the minimal memory footprint of your application is. In this article, we’ll explore how to calculate the memory size of an object or an array, including all the caveats of memory padding.
Alignment
What is alignment?
Let’s imagine we have a type of size 4, and we want to allocate memory for it. When the operating system chooses a memory chunk to give us, it needs to consider the type's size to ensure enough space is allocated. This is straightforward when using dynamic memory:
int *a = malloc(sizeof(int));
Let’s consider what the OS does underneath. Say we already have a few variables in memory — which address would malloc return?
0x00 - variable X
0x01
0x02
0x03
0x04
0x05
0x06
0x07
0x08 - variable Y
...
The OS cannot give us 0x00 because it’s already used. So maybe 0x01? Problem solved, right?
Not quite — due to how modern CPUs are designed. Each CPU has a word size, defining how much data it fetches at once. When fetching data, the CPU starts from the lowest aligned address — say 0x00 — and reads from 0x00 to 0x03. The next chunk would be 0x04 to 0x07. The CPU moves in word-sized chunks, so using addresses not divisible by the word size is inefficient or even invalid. On some platforms (e.g. ARM), accessing misaligned memory can crash the program.
The correct address for our int should be 0x04:
0x00 - variable X
0x01
0x02
0x03
0x04 - our int
0x05
0x06
0x07
0x08 - variable Y
...
So far so good, but what does this have to do with C alignment?
A lot. When we say that int has 4-byte alignment, we mean that any memory address used to store an int must be divisible by 4. A type like char with 1-byte alignment can be placed at any address, cause all numbers can be divided by 1:
printf("alignof(char) = %zu\n", alignof(char));
printf(" alignof(int) = %zu\n", alignof(int));
One might ask — why do we need both size and alignment? Aren’t they essentially the same?
They may be for atomic types like char or int, but not for arrays or structs:
int array[] = {1, 2, 3, 4};
printf("alignof(array) = %zu\n", alignof(int[4]));
printf(" sizeof(array) = %zu\n\n", sizeof(array));
The size is 16, but the alignment is 4. It makes sense: the CPU can fetch large memory chunks, but still needs to fetch them sequentially.
Let’s look at an example:
#include <stdalign.h>
#include <stdio.h>
// objects of struct S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S {
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1
// objects of struct X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X {
int n; // size: 4, alignment: 4
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
char c; // size: 1, alignment: 1
char d; // size: 1, alignment: 1
}; // size: 8, alignment: 4
int main(void) {
printf("alignof(char) = %zu\n", alignof(char));
printf(" alignof(int) = %zu\n\n", alignof(int));
int array[] = {1, 2, 3, 4};
printf("alignof(array) = %zu\n", alignof(int[4]));
printf(" sizeof(array) = %zu\n\n", sizeof(array));
printf("alignof(struct S) = %zu\n", alignof(struct S));
printf(" sizeof(struct S) = %zu\n\n", sizeof(struct S));
printf("alignof(struct X) = %zu\n", alignof(struct X));
printf(" sizeof(struct X) = %zu\n", sizeof(struct X));
}
struct X is interesting for two reasons, it shows what happens when struct fields have different alignments and it shows how to manually fulfill alignment requirements.
When we have fields with types of different alignments packed into a struct, the struct's alignment is the maximum alignment of any of its fields.
If you remove fields like c or d, alignment might no longer be satisfied — unless padding is introduced.
Padding
What is padding?
Let's go back to our struct X:
struct X {
int n; // size: 4, alignment: 4
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
char c; // size: 1, alignment: 1
char d; // size: 1, alignment: 1
}; // size: 8, alignment: 4
If we would like to delete char c, struct wouldn't longer met alignment requirements, what then?
The compiler automatically inserts padding — dummy bytes which satisfy alignment constraints:
#include <stdalign.h>
#include <stdint.h>
#include <stdio.h>
struct MyStruct {
uint8_t small;
uint32_t big;
};
int main(void) {
struct MyStruct padded = {
.small = 222,
.big = 255 + 256};
printf("sizeof(padded)=%lu\n", sizeof(padded));
printf("alignof(padded)=%lu\n", alignof(struct MyStruct));
unsigned char *padded_view = (unsigned char *)&padded;
for (int i = 0; i < (int)sizeof(padded); i++) {
printf("byte %d: %d\n", i, padded_view[i]);
}
return 0;
}
Output:
sizeof(padded)=8
alignof(padded)=4
byte 0: 222
byte 1: 159
byte 2: 196
byte 3: 120
byte 4: 255
byte 5: 1
byte 6: 0
byte 7: 0
Bytes 1–3 are padding inserted by the compiler:
byte 0: 222
byte 1: 159
byte 2: 196
byte 3: 120
Padding with array
When an array is inside a struct, the array’s alignment depends on its element type. If the element type has a larger alignment than other fields, it introduces new struct padding:
#include <stdalign.h>
#include <stdint.h>
#include <stdio.h>
struct MyStruct {
uint8_t small; // 1 byte
uint32_t big_array[3]; // 4*3 = 12 bytes
};
int main(void) {
struct MyStruct padded = {
.small = 222, // One can conclude that uint8_t takes one byte,
.big_array = // this array about 12 bytes, which gives us 13 bytes.
{255 + 256, 255 + 256, 255 + 256},
/* But once again we are wrong. We didn't consider padding. uint8_t is
padded to 4 bytes to align all struct to 4 bytes. So we have
4 + 12 = 16 bytes.
*/
};
printf("sizeof(padded)=%lu\n", sizeof(padded)); // This prints 8.
printf("alignof(padded)=%lu\n", alignof(struct MyStruct)); // This prints 4.
unsigned char *padded_view = (unsigned char *)&padded;
for (int i = 0; i < (int)sizeof(padded); i++) {
printf("byte %d: %d\n", i, padded_view[i]);
}
puts("");
return 0;
}
Now the reverse case — array of smaller elements followed by a larger field:
#include <stdalign.h>
#include <stdint.h>
#include <stdio.h>
struct MixedSizesStruct {
uint8_t small_array[3]; // 1*3 = 3 bytes
uint32_t big; // 4 bytes
};
int main(void){
struct MixedSizesStruct padded = {
.small_array = {222, 222,
222}, // One can conclude this field takes 3 bytes
.big = 511, // and this field takes 4 bytes giving us 7 bytes.
}; // But we need to consider padding. Because biggest field size is 4
// bytes, the array as a whole is aligned to 4 bytes, making 4 + 4 = 8
// bytes altogether.
printf("sizeof(padded)=%lu\n", sizeof(padded)); // This prints 8.
printf("alignof(padded)=%lu\n", // This prints 4.
alignof(struct MixedSizesStruct));
unsigned char *padded_view = (unsigned char *)&padded;
for (int i = 0; i < (int)sizeof(padded); i++) {
printf("byte %d: %d\n", i, padded_view[i]);
}
puts("");
return 0;
}
Conclusions
Next time you need to calculate the size of a type in C, you’ll be prepared. We covered alignment and padding — what they are, how they work, and why they matter when determining the memory layout of structs and arrays.