Skip to content
VLSI Mentor

C Part I · Module 10 · Structures, Unions, Enums and User-Defined Types

Structure Layout: Padding and Alignment

Four chapters have now printed a sizeof without explaining it. Two ints and a char measured 12 bytes rather than 9. A char next to a double measured 16 rather than 9. The gaps are not slack, not a compiler being wasteful, and not something you have to accept blindly — they follow from a single rule applied member by member, and once you have it you can predict any structure's size on paper before compiling. Then comes the part that makes this the one chapter in the module rated Critical: the rule depends on the order you declared the members in, so moving three lines around can shrink a structure by a third without removing a single byte of data from it. This chapter derives the rule, checks it against measured offsets, and then makes the padding visible one byte at a time.

Advanced22 min readCPart IFundamentalsTypesMemory

Part I · Module 10 · Chapter 10.5 · Structure Layout: Padding and Alignment

1. The Observation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct bad { char a; int b; char c; };

Members: one byte, four bytes, one byte. Six bytes of data.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct bad : sizeof 12  _Alignof 4   a=0 b=4 c=8

Twelve. Six bytes of data in a twelve-byte object — half of it is nothing at all. And the offsets say where the nothing is: a at 0, then b not at 1 but at 4, then c at 8, and the object runs to 12.

sizeof a structure is the distance from one member's object to the next one in an array of them — not the sum of its members. The difference is padding: unnamed bytes the compiler inserts, which belong to the structure, which you cannot access by name, and whose contents are unspecified.

The rest of this chapter is about why they are there, where exactly, and what you can do about it.

2. Alignment

Every type in C has an alignment requirement: a number such that objects of that type must start at an address which is a multiple of it. You can ask for it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
_Alignof: char 1  short 2  int 4  long 8  double 8  pointer 8

On this target, a char may start anywhere, a short must start at an even address, an int at a multiple of 4, and a double or a pointer at a multiple of 8.

Three notes before going on.

These numbers are not portable. They are properties of the target, like the sizes in Chapter 2.9. The rules in §3 are universal; the numbers are not. This is Chapter 1.3's baseline discipline again.

Alignment is usually, but not necessarily, equal to size for the basic types. Treat them as separate questions and ask _Alignof when you mean alignment.

_Alignof is the C11 spelling and works with no header. The friendlier alignof needs <stdalign.h> in C17 and became a keyword in C23. Here it is used purely as a measuring instrument; why a target has alignment requirements at all, and what happens when you violate one, is Chapter 14.4's subject.

3. The Rule, in Three Steps

This is the whole algorithm. Apply it top to bottom and you can lay out any structure with a pencil.

Step 1 — place each member in declaration order, at the next offset that is a multiple of that member's alignment. Any bytes skipped to get there are internal padding.

Step 2 — the structure's own alignment is the largest alignment among its members.

Step 3 — round the total size up to a multiple of the structure's alignment. Any bytes added are tail padding.

C guarantees two things that make this predictable: members are laid out in declaration order, and the first member is at offset 0. Everything else — how much padding, and whether any is inserted at all — is up to the implementation, and in practice every implementation does the minimum the alignment rules force.

Run the algorithm on struct bad:

StepMemberAlignmentNext multipleOffsetPadding inserted
1char a100—
2int b44 (not 1)43 bytes at 1–3
3char c188—
4struct alignment = max(1, 4, 1) = 4
5size so far 9 → round up to 123 bytes at 9–11

Predicted: offsets 0, 4, 8; _Alignof 4; sizeof 12. Measured: a=0 b=4 c=8, _Alignof 4, sizeof 12. The rule works.

4. Why Tail Padding Exists

Step 3 looks like the arbitrary one. It is the opposite — it is the step that makes arrays work.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct bad arr[3];

For arr[1] to be a usable struct bad, its b member must be 4-byte aligned. Array elements are adjacent by definition (Chapter 6.1), so element 1 starts exactly sizeof(struct bad) bytes after element 0. If sizeof were 9, element 1 would start at byte 9 and its b would land at byte 13 — not a multiple of 4.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array stride: &arr[1] - &arr[0] = 12 bytes, sizeof = 12, equal ? 1
offset of arr[1].b from arr[0] = 16  (a multiple of 4 ? 1)

The stride is sizeof, and arr[1].b lands at 16, which is a multiple of 4. Tail padding is what buys that.

Tail padding is not waste; it is the price of arr[i] being as well-aligned as arr[0]. Any structure whose size is rounded up is paying for every array of itself that anyone will ever declare.

5. The Payoff: Reorder and Shrink

Here is why c.ts rates this chapter Critical. Take the same three members and declare them in a different order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct bad  { char a; int b; char c; };     /* as before */
    struct good { int b; char a; char c; };     /* int first */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct bad : sizeof 12  _Alignof 4   a=0 b=4 c=8
struct good: sizeof  8  _Alignof 4   b=0 a=4 c=5
members in both add up to 6 bytes

Twelve bytes to eight. A third smaller. Nothing was removed, no member changed type, no data was lost, and every program using it behaves identically.

Walk the rule on struct good and you can see where the saving came from: int b at 0 needs no padding before it because 0 is a multiple of everything; char a at 4 and char c at 5 need none either; the size is 6, rounded up to 8. One pad of two bytes, instead of two pads of three.

Byte-level layout of two structures with the same three members. The first, struct bad, declares char a, then int b, then char c, and occupies twelve bytes: a at offset zero, three padding bytes, b across offsets four to seven, c at offset eight, then three more padding bytes at the tail. The second, struct good, declares int b first, then char a and char c, and occupies eight bytes: b across offsets zero to three, a at offset four, c at offset five, then two padding bytes. Same members, same alignment requirement, twelve bytes against eight.acharpadding3 bytesbint · offset 4ccharpadding3 bytesbint · offset 0acharccharpad2 bytes12
Figure 1 — the same three members laid out twice, one column per byte, so the widths are to scale. The top row is struct bad: a single byte, then three bytes thrown away to reach an offset divisible by four, the int, another single byte, and three more bytes of tail padding to round twelve. The bottom row is struct good: the int first, where it needs no run-up at all, then both chars in the bytes immediately after it, then two bytes of tail. Every byte of data is present in both rows. The top row is half again as long.

Declare members in decreasing order of alignment. Largest alignment first — double and pointers, then long, then int, then short, then char and arrays of char. It is not a guarantee of the minimum possible size, but it gets you there or close for almost every structure you will write, and it costs nothing.

Two cautions so this does not become a reflex.

Do not sacrifice a meaningful order for a few bytes. A structure with eight members whose grouping tells the reader something is worth more than four bytes. Reorder when the structure is large, or when there will be many of them.

The saving multiplies. Four bytes per structure is nothing; four bytes per structure across an array of a million is four megabytes. Size matters where count is large, and nowhere else.

6. More Worked Examples

Apply the three steps to each of these and check against the measurement.

DeclarationsizeofOffsetsWhere the padding is
struct pair { int x; int y; };80, 4none — both aligned naturally, size already a multiple of 4
struct wide { char c; double d; };160, 87 bytes after c — a double needs offset 8
struct only { char c; };10none — alignment 1, nothing to round
struct arr { char tag; int v[3]; };160, 43 after tag; v is 12 bytes, total 16, already a multiple of 4
struct empty_tail { int a; char b[3]; };80, 4none internal; b exactly fills the bytes that would have been tail padding

Three of those are worth a sentence.

struct wide loses 7 bytes to one char. Nine bytes of data, sixteen of object. Putting the double first gives sizeof 16 as well — but only because there are just two members; add a second char and the ordering starts to pay.

An array member takes its element type's alignment, not its own size. int v[3] is 12 bytes with alignment 4, which is why struct arr is 16 rather than 24.

struct empty_tail has no padding at all. char b[3] occupies bytes 4, 5 and 6, and the size rounds from 7 up to 8 — so the one tail byte is the only unused byte, and the three chars cost nothing beyond what the rounding was going to take anyway. Small members fit into space that was going to be wasted regardless, which is the second half of the reordering advice.

Nesting follows the same rule with no special case, because a nested structure is just a member with a size and an alignment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct nest { char c; struct pair p; char d; }
    sizeof 16   c=0  p=4  p.x=4  p.y=8  d=12

struct pair has alignment 4, so p goes at offset 4 after three bytes of padding; d at 12; size 13 rounds to 16. Note that offsetof reaches into the nested member — p.x at 4 and p.y at 8 — which is exactly p's own layout shifted by p's offset.

7. Padding Bytes Are Real, and They Are Not Part of the Value

Chapter 10.2 §9 said structures cannot be compared with ==, and that comparing their bytes is not a substitute. Here is the demonstration, written so that every byte read below is determinate — the whole object is painted through an unsigned char * first, which Chapter 9.12 §6 established is a defined thing to do.

Azvya Education Pvt. Ltd.VLSI Mentor
making the padding visible
#include <stdio.h>
#include <stddef.h>

struct rec { char tag; int n; };          /* 3 bytes of padding after tag */

/* write a known byte pattern over the whole object, padding included */
static void paint(struct rec *r, unsigned char fill)
{
    unsigned char *p = (unsigned char *)r;
    for (size_t i = 0; i < sizeof *r; i++) { p[i] = fill; }
}

static void dump(const char *tag, const struct rec *r)
{
    const unsigned char *p = (const unsigned char *)r;
    printf("%s bytes:", tag);
    for (size_t i = 0; i < sizeof *r; i++) { printf(" %02x", p[i]); }
    printf("   tag='%c' n=%d\n", r->tag, r->n);
}

static int same_bytes(const struct rec *a, const struct rec *b)
{
    const unsigned char *x = (const unsigned char *)a;
    const unsigned char *y = (const unsigned char *)b;
    for (size_t i = 0; i < sizeof *a; i++) { if (x[i] != y[i]) { return 0; } }
    return 1;
}

int main(void)
{
    struct rec p, q;

    paint(&p, 0xAA);            /* padding becomes AA */
    paint(&q, 0x00);            /* padding becomes 00 */

    p.tag = 'z'; p.n = 7;       /* identical member values */
    q.tag = 'z'; q.n = 7;

    dump("p", &p);
    dump("q", &q);

    printf("members equal ? %d\n", (p.tag == q.tag && p.n == q.n));
    printf("bytes   equal ? %d\n", same_bytes(&p, &q));
    printf("sizeof %zu, members total %zu, offsetof n = %zu\n",
           sizeof(struct rec), sizeof(char) + sizeof(int), offsetof(struct rec, n));

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p bytes: 7a aa aa aa 07 00 00 00   tag='z' n=7
q bytes: 7a 00 00 00 07 00 00 00   tag='z' n=7
members equal ? 1
bytes   equal ? 0
sizeof 8, members total 5, offsetof n = 4

There is the padding, in plain sight. Byte 0 is 7a — the character 'z'. Bytes 1, 2 and 3 are the padding, holding aa in one object and 00 in the other. Bytes 4 to 7 are the int 7.

Both objects have the same value. Every member compares equal.

Their bytes differ. A byte-for-byte comparison reports them different, and it is not wrong to do so — the bytes genuinely differ.

8. What You Can and Cannot Control

You control the declaration order. This is the whole of your portable influence, and §5 showed it is worth a third of the structure.

You control how many small members you use, and whether they are grouped together where they can share a padding region rather than each forcing their own.

You do not control padding directly in ISO C. There is no standard way to say "do not pad this". Compilers offer extensions for it; they are not C, they have real costs, and the judgement about when they are appropriate belongs with the problem that makes people reach for them — treating a structure as a data format — which is Chapter 14.5.

You should not assume padding is zero, write through it, or read meaning from it. Nothing promises anything about those bytes.

A measuring habit worth adopting now: when a structure matters, print sizeof and the offsetof each member once, during development. It takes four lines, it is the only way to be certain, and it will catch a member someone added in the middle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    printf("sizeof %zu  a=%zu b=%zu c=%zu\n", sizeof(struct bad),
           offsetof(struct bad, a), offsetof(struct bad, b), offsetof(struct bad, c));

9. What You Should Be Able to Say Now

  1. sizeof a structure is the array stride, not the sum of its members — verified, stride equals sizeof.
  2. Every type has an alignment requirement; on this target char 1, short 2, int 4, long/double/pointer 8. Not portable.
  3. The three-step rule: place each member at the next multiple of its own alignment, in declaration order; the structure's alignment is the largest member alignment; round the size up to a multiple of it.
  4. C guarantees declaration order and first member at offset 0; the amount of padding is the implementation's.
  5. Internal padding comes from step 1, tail padding from step 3.
  6. Tail padding exists so arr[i] is as well-aligned as arr[0] — measured: arr[1].b at byte 16, a multiple of 4.
  7. Reordering is free and can be large: the same three members went from 12 bytes to 8.
  8. Declare members in decreasing order of alignment — large first, chars last.
  9. Small members fill space the rounding was going to waste — struct empty_tail has no internal padding at all.
  10. An array member takes its element type's alignment, not its size; nesting needs no special case.
  11. Measured: two structures with equal members and different bytes — so never compare, hash or key on a structure's bytes.
  12. ISO C gives no way to suppress padding. Order is your portable control.

10. Exercises

Exercise 1 — lay them out on paper. For each, give every member's offset, the structure's alignment, and its sizeof, using §2's numbers. Then compile and check.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct a { char x; char y; int z; };
struct b { int z; char x; char y; };
struct c { char x; double d; int z; };
struct d { double d; int z; char x; };
struct e { short s; char c; short t; };

Exercise 2 — predict the saving. For each of Exercise 1's pairs that hold the same members, say how many bytes the better order saves and what fraction that is.

Exercise 3 — find the inserted member. A structure was 16 bytes and is now 24 after someone added a single char. Explain how one byte of data can cost eight, and where in the declaration it must have been added.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct before { double t; int n; int m; };
struct after  { double t; int n; char flag; int m; };

Exercise 4 — reorder for size. Reorder this to the smallest size you can, give both sizes, and say what you would check before shipping the change.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
struct record { char code; double value; char unit; int id; short rev; char pad_flag; };

Exercise 5 — explain the stride. In one paragraph, explain why sizeof must be a multiple of the structure's alignment, using the definition of an array from Chapter 6.1. What would break if it were not?

Exercise 6 — the comparison bug. A program deduplicates records by comparing their bytes and starts reporting duplicates as distinct after an unrelated refactor. Explain the mechanism using §7, and give the correct implementation.

Exercise 7 — the measuring habit. Write the four-line block you would add during development to pin down a structure's layout. Say which header you need and what change it would catch that nothing else would.

Exercise 8 — the limits of reordering. Give a structure for which decreasing-alignment order is not the smallest possible layout, or argue that none exists for the types in §2. Either answer is acceptable if the reasoning is sound.

11. Summary

  • sizeof is the array stride, not the member sum — verified.
  • Three steps: each member at the next multiple of its own alignment, in declaration order · structure alignment = largest member alignment · round the size up.
  • Declaration order and offset 0 are guaranteed; the padding amount is the implementation's.
  • Tail padding makes arr[i] as aligned as arr[0] — arr[1].b landed at byte 16.
  • Reordering is free: the same three members, 12 bytes → 8.
  • Largest alignment first; small members fill bytes the rounding would waste anyway.
  • Measured: equal members, unequal bytes — never compare, hash or key on a structure's bytes, which is why there is no ==.
  • ISO C offers no way to remove padding. Order is the portable control.
  • Print sizeof and offsetof once when a structure matters.

The layout rule is now complete, and it describes one structure in isolation. The reason it matters in practice is the thing §4 and §5 kept pointing at without ever being about it: structures are rarely used one at a time. A program that holds one reading holds a thousand, and the moment it does, every byte of padding is multiplied by the count, the stride becomes the thing traversal depends on, and the way you walk the collection determines whether the code reads like a table or like bookkeeping. The next chapter builds those tables — declaring them, initialising them, passing them, and traversing them both ways — and it is where the array rules from Module 6 and the pointer rules from Module 7 finally meet a record type.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.