Understanding the size of a struct in C is essential for writing efficient, portable code, especially when dealing with memory‑constrained environments, network protocols, or binary file formats. The compiler determines the total bytes a struct occupies based on the types of its members, their alignment requirements, and any padding it inserts to satisfy those requirements. This article explains how the size is calculated, what influences it, and how you can predict or control it in your programs.
Introduction
In C, a struct (short for structure) is a user‑defined type that groups heterogeneous data members under a single name. So naturally, the size of a struct in C is often larger than the simple sum of its members’ sizes. Now, while the logical layout is straightforward, the physical layout in memory may contain unused bytes—known as padding—to align each member on an address that matches its natural boundary. Knowing why and how this happens helps you avoid subtle bugs, optimize memory usage, and ensure data compatibility across different platforms Small thing, real impact..
Understanding Struct Memory Layout
Basic Concepts
- sizeof operator: Returns the total number of bytes a type occupies. For a struct,
sizeof(struct_name)yields the size including any padding. - Alignment requirement: Each fundamental type has an alignment value (usually equal to its size, but not always). To give you an idea, on most 32‑bit systems
intis 4 bytes and aligned to a 4‑byte boundary;doubleis 8 bytes and aligned to an 8‑byte boundary. - Padding bytes: Extra bytes inserted by the compiler so that each member starts at an address that is a multiple of its alignment requirement. Padding may appear between members (internal padding) and after the last member (tail padding) to make the whole struct size a multiple of its strictest alignment.
How the Compiler Calculates Size
- Determine the maximum alignment (
max_align) among all members. - Place the first member at offset 0.
- For each subsequent member:
- Compute the current offset (
cur). - Increase
curto the next multiple of the member’s alignment (cur = ((cur + align - 1) / align) * align). - Place the member at that offset, then advance
curby the member’ssizeof.
- Compute the current offset (
- After the last member, increase
curto the next multiple ofmax_alignto obtain the final struct size.
This algorithm guarantees that any array of the struct will keep each element properly aligned Easy to understand, harder to ignore..
Factors Affecting Struct Size
1. Member Types and Their Sizes
The primitive types (char, short, int, long, float, double, pointers) each have a fixed size on a given target architecture. Changing a member’s type directly changes the contribution to the total size Worth knowing..
2. Alignment Requirements
Alignment is often the biggest source of hidden overhead. Here's a good example: a struct containing a char (1 byte) followed by an int (4 bytes) will typically have 3 bytes of padding after the char to align the int on a 4‑byte boundary And that's really what it comes down to..
3. Order of Declaration
Reordering members can reduce or increase padding. Placing larger‑aligned members first often yields a tighter layout Easy to understand, harder to ignore. Turns out it matters..
4. Compiler‑Specific Attributes
Attributes such as __attribute__((packed)) (GCC/Clang) or #pragma pack (MSVC) instruct the compiler to disable padding, forcing the struct to occupy exactly the sum of its members’ sizes. Use with caution, as misaligned access can cause performance penalties or hardware faults on some platforms Not complicated — just consistent. Less friction, more output..
You'll probably want to bookmark this section.
5. Bit‑Fields
Bit‑fields allow multiple logical fields to share the same storage unit. Their packing rules are more complex; the compiler may still insert padding to respect the underlying type’s alignment, but the overall size can be smaller than a comparable struct of separate variables Small thing, real impact. No workaround needed..
6. Struct Nesting
When a struct contains another struct as a member, the inner struct’s size and alignment contribute to the outer struct’s layout exactly as if it were a single member of that size and alignment Which is the point..
Practical Examples
Below are several illustrative snippets. Assume a typical 64‑bit Linux environment where:
char= 1 byte, alignment 1short= 2 bytes, alignment 2int= 4 bytes, alignment 4long= 8 bytes, alignment 8double= 8 bytes, alignment 8- Pointers = 8 bytes, alignment 8
Example 1: Simple Struct
struct S1 {
char a; // 1 byte
int b; // 4 bytes
};
aplaced at offset 0 (size 1).- Next offset = 1 → rounded up to next multiple of 4 → offset 4 (3 bytes padding).
bplaced at offset 4 (size 4).- End offset = 8 → max_align = 4 → size already multiple of 4.
Result: sizeof(struct S1) == 8 bytes.
Example 2: Reordered Members
struct S2 {
int b; // 4 bytes
char a; // 1 byte
};
bat offset 0 (size 4).aat offset 4 (size 1).- End offset = 5 → max_align = 4 → round up to 8.
Result: sizeof(struct S2) == 8 bytes (same size, but padding moved to the end).
If we add another char:
struct S3 {
int b; // 4
char a; // 1
char c; // 1
};
boffset 0‑3.aoffset 4.coffset 5.- End offset = 6 → round up to 8.
Result: Still 8 bytes; the two chars share the same 4‑byte word as padding Worth keeping that in mind..
Example 3: Double and Char
struct S4 {
double d; // 8 bytes
char c; // 1 byte
};
doffset 0‑7.coffset 8 → needs alignment 1, so placed directly.- End offset = 9 → max_align = 8 → round up to 16.
Result: `
Example 3 (completed): Double and Char
struct S4 {
double d; // 8 bytes, alignment 8
char c; // 1 byte, alignment 1
};
doccupies offsets 0‑7.ccan be placed immediately after, at offset 8 (no padding required because its alignment is 1).- The struct’s total size before final rounding is 9 bytes.
- The struct’s alignment requirement is the maximum of its members, i.e. 8 (from
double). - Therefore the size is rounded up to the next multiple of 8 → 16 bytes.
Result: sizeof(struct S4) == 16 bytes.
The char occupies only 1 byte of the 8‑byte trailing padding that follows d Not complicated — just consistent..
Example 4: Pointer, Integer, and Char
struct S5 {
void *ptr; // 8 bytes, alignment 8
int i; // 4 bytes, alignment 4
char ch; // 1 byte, alignment 1
};
ptrstarts at offset 0 (size 8).imust be aligned to 4; the next free offset is 8, which is already a multiple of 4, soistarts at offset 8 (size 4).chfollows at offset 12 (size 1).- End offset = 13. The struct’s alignment is still 8 (from
ptr), so the size is rounded up to 16 bytes.
Result: sizeof(struct S5) == 16 bytes.
Only 3 bytes of tail padding are needed after ch to satisfy the 8‑byte alignment Worth keeping that in mind..
Example 5: Forced Packing with __attribute__((packed))
struct S6 {
double d; // 8 bytes
char flag; // 1 byte
} __attribute__((packed));
doccupies offsets 0‑7.flagis placed directly after at offset 8 (no padding).- The struct’s size is the sum of its members: 9 bytes.
- Because the
packedattribute disables all padding, the alignment requirement of the struct drops to 1.
Result: sizeof(struct S6) == 9 bytes.
Caution: Accessing d on architectures that require naturally aligned accesses (e.g., most ARM cores) may generate an alignment fault or incur a performance hit The details matter here..
Example 6: Bit‑Fields in a Struct
struct S7 {
unsigned int flags : 3; // 3 bits
unsigned int enable : 1; // 1 bit
unsigned int reserved : 28; // remaining bits of the same storage unit
char name[4]; // 4 bytes, alignment 1
};
- The three bit‑fields together occupy 32 bits (4 bytes) because the compiler chooses the smallest unsigned integer type that can hold them, which is
unsigned int(4‑byte alignment). - Offsets: bit‑fields start at offset 0 inside the first 4‑byte word; the next member
namebegins at offset 4. nameis a character array, each element is 1 byte, alignment 1, so it occupies offsets 4‑7.- End offset = 8; the struct’s alignment is the maximum of its members (
unsigned int→ 4), so the size is rounded up to 8 bytes.
Result: sizeof(struct S7) == 8 bytes.
Bit‑fields can dramatically reduce storage for small logical values, but they may still incur padding to satisfy the underlying integer’s alignment.
Example 7: Nested Structs
### Example 7: Nested Structs
```c
struct Outer {
struct Inner { int a; char b; } inner; /* 4‑byte aligned, size 4 */
double value; /* 8 bytes, alignment 8 */
};
Innerconsists of anint(4 bytes, alignment 4) followed by achar(1 byte, alignment 1). The compiler padsInnerto a 4‑byte boundary, making its size 4 bytes.- In
Outer,innerappears as the first member, so it starts at offset 0. - After
innerfinishes at offset 4, the next membervalue(requiring 8‑byte alignment) cannot begin at offset 4; it must start at the nearest multiple of 8, which is offset 8. This means four bytes of internal padding are inserted betweeninnerandvalue. valuetherefore occupies offsets 8–15, giving a final byte count of 12.
Result: sizeof(struct Outer) == 12 bytes.
The nesting forces the larger member to shift forward, illustrating how sub‑structures contribute their own padding requirements to the parent Simple as that..
Example 8: Union Aliasing and Alignment
union U1 {
float f; /* 4 bytes, alignment 4 */
char c[4]; /* 4 bytes, alignment 1 */
};
- A union does not introduce additional alignment constraints beyond those required by its constituent types. Both
fandc[4]fit comfortably into a single 4‑byte word. - The compiler typically stores the union as either the
floatfield alone (if it is the first member) or as two consecutive 4‑byte words when the second member requires higher alignment. - Here,
foccupies offsets 0–3, andcfollows immediately at offset 4, yielding a struct size of 4 bytes instead of the naive 8‑byte sum.
Result: sizeof(U1) == 4 bytes.
Note that while unions allow sharing of memory, the programmer must avoid accessing both fields simultaneously through different pointers, otherwise undefined behavior occurs.
Example 9: Repeated Members and Array Storage
struct T {
int arr[3]; /* 3 × 4 = 12 bytes, natural alignment 4 */
};
arris an array of threeints. Eachintis 4 bytes and requires 4‑byte alignment, so the whole array naturally aligns to 4 bytes without extra padding.- The struct itself inherits the minimum alignment among its members, which is 4.
- Hence
sizeof(struct T) == 12bytes — no hidden padding is added.
Conclusion
The layout of a C structure is determined by a combination of member sizes, required alignments, and necessary padding to keep each member at a position that satisfies its own alignment constraint. When a struct contains another struct,
When a struct contains another struct, the compiler must respect the inner structure’s alignment as well as the outer members’, which can lead to additional padding beyond what would be expected from a simple size summation.
Example 10: Nested Structure with Larger Alignment
struct N {
double d; // 8 bytes, alignment 8
struct {
int a; // 4 bytes, alignment 4
char b; // 1 byte, alignment 1
} sub;
};
The sub member occupies 4 bytes and is naturally aligned to a 4‑byte boundary. After sub the next member, d, requires an 8‑byte boundary, so the compiler inserts four padding bytes. The layout is:
sub: offset 0‑3 (4 bytes)- padding : offset 4‑7 (4 bytes)
d: offset 8‑15 (8 bytes)
Hence sizeof(struct N) equals 16 bytes, a multiple of the struct’s alignment (8).
Example 11: Pointer to a Nested Structure
struct Q {
struct N *np; // 8‑byte pointer, alignment 8
struct N n; // same layout as in Example 10, 16 bytes
};
The pointer np starts at offset 0 and occupies bytes 0‑7. The n member begins at offset 8, which already satisfies its 8‑byte alignment requirement, so no extra padding is introduced. The total size of struct Q is 24 bytes (8 + 16) But it adds up..
Example 12: Bit‑field Packing
struct B {
unsigned int x : 2; // 2‑bit field
unsigned int y : 6; // 6‑bit field
unsigned int z : 8; // 8‑bit field
};
A typical implementation packs the three bit‑fields into a single 32‑bit word, requiring only 4 bytes. On the flip side, the compiler may insert a single padding byte after the second field to align the third field to a 1‑byte boundary, but the overall size remains 4 bytes. This illustrates that, unlike ordinary members, bit‑fields can reduce the amount of padding compared with their underlying integer types.
Conclusion
Simply put, the size of a C structure is not merely the sum of its constituents. Also, each member must be placed at an offset that satisfies its alignment requirement, and the compiler inserts padding both inside larger members and between members to preserve those constraints. Practically speaking, when a structure contains another structure, the inner object’s own alignment rules propagate outward, often causing the outer object to allocate more bytes than a naïve count would suggest. Understanding how compilers apply these rules enables developers to predict layout, avoid surprising size differences across platforms, and write code that behaves consistently whether it runs on a 32‑bit or 64‑bit target.