You define a struct with a char, an int, and another char. You expect sizeof to return 6. It returns 12. The compiler inserted 5 bytes of padding you didn’t ask for. Here is why, and how to control it.
Why Padding Exists
#include <stdio.h>
struct A {
char c; /* 1 byte */
int n; /* 4 bytes */
char d; /* 1 byte */
};
int main() {
printf("sizeof(struct A) = %zun", sizeof(struct A)); /* 12, not 6 */
return 0;
}Modern CPUs read integers faster when they are stored at addresses that are multiples of their size. An int at address 5 requires two memory bus transactions on most architectures — or causes a hardware exception on strict alignment platforms (like many ARM cores without the unaligned access extension).
To ensure n starts at a 4-byte-aligned address, the compiler inserts 3 bytes of padding after c. Then d is 1 byte, but the struct must end at a multiple of the largest member’s alignment (4), so 3 more bytes are added at the end. Total: 1 + 3 (pad) + 4 + 1 + 3 (pad) = 12.
Visualizing the Layout
struct A layout:
Offset 0: c (1 byte)
Offset 1-3: padding (3 bytes)
Offset 4: n (4 bytes)
Offset 8: d (1 byte)
Offset 9-11: padding (3 bytes)
Total: 12 bytesReordering Fields to Eliminate Padding
#include <stdio.h>
struct Bad {
char c; /* 1 byte */
int n; /* 4 bytes */
char d; /* 1 byte */
/* size: 12 bytes */
};
struct Good {
int n; /* 4 bytes */
char c; /* 1 byte */
char d; /* 1 byte */
/* size: 8 bytes — 2 trailing pad bytes */
};
struct Best {
int n; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
char d; /* 1 byte */
/* size: 8 bytes — no padding needed */
};
int main() {
printf("Bad: %zun", sizeof(struct Bad)); /* 12 */
printf("Good: %zun", sizeof(struct Good)); /* 8 */
printf("Best: %zun", sizeof(struct Best)); /* 8 */
return 0;
}The rule: sort fields from largest to smallest. Put all the multi-byte types first, then the single-byte types last. This eliminates interior padding in most cases and minimizes trailing padding.
Checking Offsets with offsetof
#include <stdio.h>
#include <stddef.h>
struct Point3D {
double x; /* 8 bytes */
double y; /* 8 bytes */
double z; /* 8 bytes */
int id; /* 4 bytes */
char tag; /* 1 byte */
};
int main() {
printf("x offset: %zun", offsetof(struct Point3D, x)); /* 0 */
printf("y offset: %zun", offsetof(struct Point3D, y)); /* 8 */
printf("z offset: %zun", offsetof(struct Point3D, z)); /* 16 */
printf("id offset: %zun", offsetof(struct Point3D, id)); /* 24 */
printf("tag offset: %zun", offsetof(struct Point3D, tag)); /* 28 */
printf("total size: %zun", sizeof(struct Point3D)); /* 32 */
return 0;
}offsetof(type, member) gives the byte offset of a member within a struct. Use it when you need to parse binary data by field position or when you are implementing a generic introspection system.
Packed Structs — Removing All Padding
#include <stdio.h>
struct __attribute__((packed)) NetworkHeader {
uint8_t version;
uint8_t flags;
uint16_t length;
uint32_t sequence;
uint32_t checksum;
};
int main() {
printf("sizeof NetworkHeader: %zun", sizeof(struct NetworkHeader));
/* 12 bytes — matches the wire format exactly */
return 0;
}__attribute__((packed)) is a GCC extension that removes all padding. Use it only when reading/writing binary wire protocols or file formats where the layout is mandated externally. On architectures without hardware unaligned access support, accessing packed struct members can crash or be dramatically slower.
Alignment with _Alignas (C11)
#include <stdio.h>
#include <stdalign.h>
struct CacheFriendly {
_Alignas(64) char data[64]; /* start on a cache line boundary */
int count;
};
int main() {
struct CacheFriendly obj;
printf("alignment: %zun", _Alignof(struct CacheFriendly)); /* 64 */
return 0;
}_Alignas(N) forces a member or variable to start at an N-byte boundary. This matters for high-performance code where false sharing between CPU cache lines causes contention. Aligning a frequently-written field to a cache line (64 bytes on x86) prevents cores from invalidating each other’s caches.
For more on how memory layout affects struct behavior, see our guide on C structs and typedef. You can also verify any struct size and offset calculations in our c compiler — paste the code and run it to see actual values on the underlying platform.
TL;DR
- The compiler adds padding so each field starts at an address that is a multiple of its size
- Structs are sized to be a multiple of the largest member’s alignment
- Reorder fields large-to-small to minimize padding without changing semantics
- Use
offsetof()to get exact field byte offsets __attribute__((packed))removes all padding — use only for wire/file formats, not general structs_Alignas(N)forces specific alignment for performance-critical data structures- Never cast a packed struct pointer to a regular pointer and dereference it — alignment mismatch causes UB