Size Of A Struct In C

11 min read

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 int is 4 bytes and aligned to a 4‑byte boundary; double is 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

  1. Determine the maximum alignment (max_align) among all members.
  2. Place the first member at offset 0.
  3. For each subsequent member:
    • Compute the current offset (cur).
    • Increase cur to the next multiple of the member’s alignment (cur = ((cur + align - 1) / align) * align).
    • Place the member at that offset, then advance cur by the member’s sizeof.
  4. After the last member, increase cur to the next multiple of max_align to 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 1
  • short = 2 bytes, alignment 2
  • int = 4 bytes, alignment 4
  • long = 8 bytes, alignment 8
  • double = 8 bytes, alignment 8
  • Pointers = 8 bytes, alignment 8

Example 1: Simple Struct

struct S1 {
    char  a;   // 1 byte
    int   b;   // 4 bytes
};
  • a placed at offset 0 (size 1).
  • Next offset = 1 → rounded up to next multiple of 4 → offset 4 (3 bytes padding).
  • b placed 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
};
  • b at offset 0 (size 4).
  • a at 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
};
  • b offset 0‑3.
  • a offset 4.
  • c offset 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
};
  • d offset 0‑7.
  • c offset 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
};
  • d occupies offsets 0‑7.
  • c can 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
};
  • ptr starts at offset 0 (size 8).
  • i must be aligned to 4; the next free offset is 8, which is already a multiple of 4, so i starts at offset 8 (size 4).
  • ch follows 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));
  • d occupies offsets 0‑7.
  • flag is placed directly after at offset 8 (no padding).
  • The struct’s size is the sum of its members: 9 bytes.
  • Because the packed attribute 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 name begins at offset 4.
  • name is 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 */
};
  • Inner consists of an int (4 bytes, alignment 4) followed by a char (1 byte, alignment 1). The compiler pads Inner to a 4‑byte boundary, making its size 4 bytes.
  • In Outer, inner appears as the first member, so it starts at offset 0.
  • After inner finishes at offset 4, the next member value (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 between inner and value.
  • value therefore 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 f and c[4] fit comfortably into a single 4‑byte word.
  • The compiler typically stores the union as either the float field alone (if it is the first member) or as two consecutive 4‑byte words when the second member requires higher alignment.
  • Here, f occupies offsets 0–3, and c follows 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 */
};
  • arr is an array of three ints. Each int is 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) == 12 bytes — 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.

What Just Dropped

Just Went Up

You'll Probably Like These

You Might Want to Read

Thank you for reading about Size Of A Struct In C. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home