Question

Structure Padding and Packing in C

cstructstructurepaddingpacking

Question

Consider these C structure definitions:

struct mystruct_A {
    char a;
    int b;
    char c;
} x;

struct mystruct_B {
    int b;
    char a;
} y;

On a particular system, sizeof(struct mystruct_A) is 12 bytes and sizeof(struct mystruct_B) is 8 bytes.

Are these structures padded or packed? When does structure padding occur, and when is explicit structure packing used?

Short Answer

You will learn how C compilers arrange structure members in memory, why unused padding bytes appear, how alignment affects sizeof, and when packing directives are appropriate.

Concept

A C struct stores its members in declaration order, but the compiler may insert padding bytes between members and at the end of the structure.

Padding exists because many CPUs access values such as int, long, double, and pointers more efficiently—or sometimes only correctly—when they begin at suitably aligned memory addresses.

For example, on many common systems:

  • char needs alignment of 1 byte.
  • int needs alignment of 4 bytes.

Given this structure:

struct mystruct_A {
    char a;
    int b;
    char c;
};

A likely layout is:

byte:  0      1  2  3      4  5  6  7      8      9 10 11
       a      padding       b (int)        c      tail padding
  • a occupies byte 0.
  • The compiler inserts 3 bytes before b so that b starts at byte 4.
  • b occupies bytes 4–7.
  • c occupies byte 8.
  • The compiler adds 3 tail-padding bytes so that arrays of this structure keep each element properly aligned.

This produces a size of 12 bytes.

mystruct_B also has padding on a typical system:

struct mystruct_B {
    int b;
    char a;
};
byte:  0  1  2  3      4      5  6  7
       b (int)        a      tail padding

It has no internal padding between its members, but it has 3 bytes of padding at the end, producing a size of 8 bytes.

Therefore, with the stated sizes, both structures are padded. Neither is necessarily explicitly packed.

The exact layout is implementation-dependent. It can vary with the compiler, CPU architecture, ABI, compiler options, and the sizes and alignment requirements of the involved types. Use sizeof and _Alignof to inspect the current build environment.

Mental Model

Think of memory as a row of parking spaces.

  • A char is a motorcycle: it can park in any one space.
  • An int is a larger vehicle that must begin at parking-space numbers divisible by 4.

If a char is parked first at space 0, the next available space is 1. But an int that requires a multiple-of-4 starting position cannot begin there. Spaces 1–3 are left empty as padding, and the int starts at space 4.

At the end, extra spaces may be reserved so that the next structure in an array starts at a correctly aligned position too.

Take Quiz

Syntax and Examples

C does not provide one fully portable standard syntax to force packed layouts. Compilers commonly provide extensions or pragmas. Before using them, first understand the normal layout.

#include <stdio.h>
#include <stddef.h>

struct Record {
    char tag;
    int id;
    char status;
};

int main(void) {
    printf("sizeof(struct Record) = %zu\n", sizeof(struct Record));
    printf("tag offset = %zu\n", offsetof(struct Record, tag));
    printf("id offset = %zu\n", offsetof(struct Record, id));
    printf("status offset = %zu\n", offsetof(struct Record, status));
}

On a system where int is 4-byte aligned, typical output is:

sizeof(struct Record) = 12
tag offset = 0
id offset = 4
status offset = 8

offsetof reveals each member's location relative to the start of the structure. sizeof includes every member plus any internal and trailing padding.

Reducing padding by reordering members

Place members with stricter alignment requirements first when the member order is not semantically important:

struct BetterRecord {
    int id;
    char tag;
    char status;
};

This often has a size of 8 bytes rather than 12 bytes:

bytes 0-3: id
byte  4:   tag
byte  5:   status
bytes 6-7: tail padding

Compiler-specific packed structures

GCC and Clang commonly support __attribute__((packed)):

struct __attribute__((packed)) PackedHeader {
    char type;
    int length;
    char flags;
};

Microsoft C compilers commonly support #pragma pack:

#pragma pack(push, 1)
struct PackedHeader {
    char type;
    int length;
    char flags;
};
#pragma pack(pop)

These mechanisms are not portable C, and accessing unaligned members can be slower or unsafe on some architectures.

Step by Step Execution

Trace the usual layout of mystruct_A on a system where char has alignment 1 and int has alignment 4:

struct mystruct_A {
    char a;
    int b;
    char c;
};
  1. The structure starts at offset 0.
  2. a needs alignment 1, so it is placed at offset 0. It uses 1 byte.
  3. The next free offset is 1.
  4. b needs an offset divisible by 4. Offset 1 is not valid, so the compiler inserts 3 padding bytes at offsets 1, 2, and 3.
  5. b starts at offset 4 and occupies 4 bytes: offsets 4 through 7.
  6. c needs alignment 1, so it is placed immediately at offset 8.
  7. The next free offset is 9. The whole structure commonly needs an alignment of 4 because int is its strictest member.
  8. To make its total size a multiple of 4, 3 tail-padding bytes are added at offsets 9, 10, and 11.
  9. The final size is 12 bytes.

Now trace mystruct_B:

struct mystruct_B {
    int b;
    char a;
};
  1. b starts at offset 0 and occupies offsets 0–3.
  2. a is placed at offset 4.
  3. The next free offset is 5.
  4. Three tail-padding bytes are added so that the structure size is a multiple of 4.
  5. The final size is 8 bytes.

Real World Use Cases

  • Memory-sensitive arrays: Millions of records can consume substantially less memory when members are ordered to reduce padding.
  • Network protocols: Protocol headers often require an exact byte layout. Developers may use packed representations carefully, then convert byte order with functions such as ntohs and ntohl.
  • Binary file formats: File headers and device data formats may define exact offsets and field sizes.
  • Hardware registers: Embedded code sometimes maps structures onto memory-mapped registers. This requires platform-specific rules and careful use of volatile.
  • Foreign function interfaces: C structures passed to operating-system APIs or libraries must use the layout expected by that API's ABI.
  • Serialization: Raw structure memory is usually a poor serialization format because padding, byte order, and type sizes can differ between systems.
Take Quiz

Real Codebase Usage

In ordinary application code, developers usually let the compiler add normal padding. It gives efficient, naturally aligned access.

Common project practices include:

  • Reorder members intentionally: Put pointers, double, long, and other highly aligned members before smaller fields when doing so does not reduce clarity.
  • Use fixed-width types for external formats: Prefer uint8_t, uint16_t, and uint32_t from <stdint.h> when a protocol requires fixed field widths.
  • Validate binary layouts: Use _Static_assert to fail compilation when an expected layout changes.
#include <stdint.h>

struct MessageInfo {
    uint32_t id;
    uint16_t kind;
    uint8_t flags;
    uint8_t version;
};

_Static_assert(sizeof(struct MessageInfo) == 8,
               "Unexpected MessageInfo size");
  • Keep wire-format handling separate: A robust design often reads bytes into a buffer and explicitly decodes each field instead of casting a network buffer to a structure pointer.
  • Scope packing directives: If a platform-specific packed type is unavoidable, limit packing to that one type. For example, use #pragma pack(push, 1) and #pragma pack(pop) rather than leaving a changed packing setting active for later declarations.

Common Mistakes

Assuming padding is identical everywhere

This assumption is unsafe:

/* Do not assume this is always 12 bytes. */
struct Example {
    char a;
    int b;
    char c;
};

Different targets can use different alignments or type sizes. Check with sizeof, offsetof, documentation for the target ABI, and compile-time assertions where an exact layout is required.

Assuming a smaller field always removes padding

Changing int to a smaller type can change the layout, but alignment still matters. Always measure the actual layout rather than guessing.

Treating padding bytes as initialized data

Assigning members does not necessarily initialize padding bytes:

struct mystruct_A value;
value.a = 'X';
value.b = 42;
value.c = 'Y';

The padding bytes may contain unspecified values. Do not compare entire structures with memcmp unless you have deliberately initialized the entire object and understand the consequences. Do not write raw structure bytes to a file or network as a portable format.

Packing every structure to save memory

Packed members can be unaligned:

struct __attribute__((packed)) BadIdeaForGeneralUse {
    char tag;
    int id;
};

On some CPUs, accessing id may cost extra instructions or can fault. Prefer normal structures and member reordering unless an external byte layout requires packing.

Forgetting tail padding

Beginners often add only member sizes. For int plus char, they may expect 5 bytes. The compiler may make the total 8 bytes so this works correctly:

struct mystruct_B items[2];

Each items[i] begins at an address suitable for its int b member.

Using #pragma pack without restoring it

A packing pragma may affect later declarations in the same file. Use matching push and pop directives when supported by the compiler.

Comparisons

TermMeaningTypical effect
AlignmentA requirement or preference that an object begins at a particular address boundaryAn int may need an offset divisible by 4
PaddingUnused bytes inserted by the compiler to satisfy alignment rulesMakes a structure larger than the sum of its visible members
Tail paddingPadding after the final memberKeeps every element aligned when structures are stored in an array
PackingAn explicit request to reduce or remove normal padding, usually via compiler-specific featuresProduces compact layouts but may create unaligned accesses
StructureTypical layout with 4-byte int alignmentTypical size
char; int; char;1 byte, 3 internal padding bytes, 4-byte int, 1 byte, 3 tail-padding bytes12 bytes
int; char;4-byte int, 1 byte, 3 tail-padding bytes8 bytes
int; char; char;4-byte int, two 1-byte fields, 2 tail-padding bytes8 bytes

Packing is not the same as reordering:

  • Reordering keeps normal alignment and is generally safe and efficient.
  • Packing changes alignment/layout rules and should be reserved for a defined external layout or similar special requirement.
Take Quiz

Cheat Sheet

  • A structure's members appear in declaration order, but padding may be inserted between or after them.
  • Padding helps satisfy alignment requirements.
  • sizeof(struct T) includes member bytes and padding bytes.
  • Tail padding helps ensure struct T array[n] keeps every element correctly aligned.
  • char commonly has alignment 1; int commonly has alignment 4, but these are not universal C guarantees.
  • Inspect sizes with:
sizeof(struct T)
_Alignof(struct T)
offsetof(struct T, member)
  • Include <stddef.h> for offsetof.
  • Put larger or more strictly aligned members first to often reduce wasted space.
  • Normal padded structures are the default and are usually preferable.
  • Packed structures use compiler-specific features such as GCC/Clang __attribute__((packed)) or MSVC #pragma pack.
  • Avoid raw struct memory as a portable file or network format.
  • Do not assume padding bytes have useful initialized values.

FAQ

Are mystruct_A and mystruct_B padded?

With the reported sizes of 12 and 8 on a typical 4-byte-aligned int system, yes. mystruct_A has internal and tail padding; mystruct_B has tail padding.

Is mystruct_B packed because its int comes first?

No. Ordering int before char avoids internal padding, but the structure can still have tail padding. Packing means explicitly changing the compiler's normal layout rules.

Why does C add padding after the final member?

It ensures every element in an array of structures starts at a suitable alignment boundary for all of its members.

Can I rely on a structure having the same size on every computer?

No. Sizes and offsets depend on the compiler, target architecture, ABI, compiler settings, and member types.

How can I find the offset of a C structure member?

Use offsetof(struct Type, member) from <stddef.h>. It reports the byte offset from the start of the structure.

Should I use packed structures to reduce memory use?

Usually no. First consider reordering members. Use explicit packing mainly when interacting with a format that specifies exact byte offsets.

Is it safe to send a C structure directly over a network?

Usually not. Padding bytes, endianness, type widths, and compiler layout differences make raw structure bytes non-portable and potentially unsafe.

Does assigning every member initialize padding bytes?

Not necessarily. Member assignments write member storage; padding bytes may remain unspecified.

Take Quiz

Mini Project

Description

Build a small C program that reports a structure's size, alignment, and member offsets. It demonstrates that member order can change internal padding while preserving normal, efficient alignment.

Goal

Compare two record layouts and identify the one that uses less memory on the current compiler.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more