FirmGod

C Struct Padding Visualizer

Paste a C struct and see its exact byte layout: every member offset, each padding hole, the total size, and a member ordering that wastes less space.

long and pointers are 8 bytes.

Examples
Total size16 bytes
Alignment4 bytes
Padding5 bytes
Wasted31%

Byte map

Eight bytes per row. Hatched cells are padding the compiler inserts and you cannot use.

0
8

Members

OffsetSizeAlignTypeMember
011uint8_ttype
13padding — 3 bytes the compiler inserts
444uint32_ttimestamp
811uint8_tflags
91padding — 1 byte the compiler inserts
1022uint16_tlength
1231uint8_tpayload[3]
151padding — 1 byte the compiler inserts

Reordering saves 4 bytes

Declaring members from widest alignment to narrowest removes every interior hole, bringing the struct from 16 bytes down to 12. Only do this where the declaration order is yours to choose — a struct that mirrors a wire format or a hardware register block must keep its layout.

struct Packet {
    uint32_t timestamp;
    uint16_t length;
    uint8_t payload[3];
    uint8_t type;
    uint8_t flags;
};

The rules the compiler follows

Struct layout in C is determined by three rules applied in order. There is no cleverness and no reordering — a conforming compiler must lay members out in declaration order.

  1. Every member starts at a multiple of its alignment. A four-byte integer must begin at an offset divisible by four. If the previous member ended somewhere else, the compiler inserts padding bytes until it does.
  2. The struct's alignment is the largest alignment among its members. One doubleis enough to force the entire struct onto an eight-byte boundary.
  3. The struct's size is rounded up to a multiple of its alignment. This is the tail padding, and it exists so that arrays of the struct keep every element aligned.

What alignment actually buys

Alignment is not bureaucracy. A load instruction reads a naturally aligned word from memory in one bus transaction. A value straddling a word boundary needs two, plus shifting and merging. On x86 the hardware hides this and you pay only in cycles, but on many Cortex-M parts an unaligned 32-bit load raises a usage fault, and on older ARM cores it silently returns rotated data — a bug that is very hard to find because nothing crashes.

Ordering members to save space

Because members cannot be reordered by the compiler, the declaration order decides how much space is lost. A struct written in the order the fields came to mind can easily be a third larger than the same fields sorted by width:

Declaration orderLayoutSize
char, int, char1 byte, 3 padding, 4 bytes, 1 byte, 3 padding12 bytes
int, char, char4 bytes, 1 byte, 1 byte, 2 padding8 bytes

Sorting members from widest alignment to narrowest is optimal for a struct with no bitfields: it leaves no interior holes at all, and only the tail can still contain padding. The tool suggests that ordering whenever it would be smaller than what you wrote.

Whether to apply it is a judgement call. On a part with 8 KB of RAM holding an array of a thousand records, four bytes per record is half your memory. For a struct that exists once, grouping related fields together is worth more than the bytes.

When you must not reorder

Some structs have a layout that is part of their contract, and rearranging them breaks something outside your file: hardware register blocks mapped over a peripheral, structures shared with another compiler or language, anything cast over a received buffer, and structs whose layout a published ABI defines. In those cases the padding is not waste — it is the layout the other side expects.

Packing, and what it costs

#pragma pack(n) and __attribute__((packed)) cap how far the compiler may align each member, which removes padding at the cost of misaligned access. Compilers respond by generating byte-at-a-time loads and stores for those members, so a packed struct can be several times slower to read than its natural counterpart.

The sharper problem is pointers. Taking the address of a misaligned member produces a pointer the compiler cannot assume is aligned, and passing it anywhere that expects a normal pointer is undefined behaviour. GCC and Clang warn about this with -Waddress-of-packed-member, and the warning is worth taking seriously rather than silencing.

Bitfields

Bitfields let you name individual bits, which makes register definitions readable. What they do not give you is a portable layout. The standard says a bitfield is allocated within an addressable storage unit, but leaves the order within that unit to the implementation, and compilers genuinely differ — GCC and Clang continue packing into the current unit while MSVC starts a new one whenever the declared type changes.

For registers on a device you compile for with one known toolchain, bitfields are fine and pleasant to read. For anything that crosses a compiler, an architecture, or a wire, shifts and masks against auint32_t express the same thing with no ambiguity.

Checking your assumptions in the build

When a layout matters, assert it rather than trusting it. A static assertion costs nothing at runtime and turns a silent mismatch into a compile error:

_Static_assert(sizeof(struct Frame) == 16, "Frame layout changed");

Pair it with offsetof checks on the members that matter. Adding a field years later then fails the build instead of corrupting a protocol in the field.

Frequently asked questions

Why is my struct bigger than the sum of its members?

Each member has to start at an offset that is a multiple of its own alignment, so the compiler inserts unused bytes wherever the next member would otherwise land on a bad boundary. Those bytes are padding. The struct is then rounded up to a multiple of its own alignment, which adds tail padding on top.

Why is there padding at the very end?

So that arrays work. If the struct size were not a multiple of its alignment, the second element of an array of that struct would start on a misaligned address. Rounding the size up guarantees every element stays aligned.

Should I just use packed everywhere?

No. Packing produces misaligned members, and reading those is slower on x86 and can fault outright on some ARM cores, particularly for 64-bit loads and floating point. Compilers also generate byte-by-byte access for members of a packed struct, and taking a pointer to one is undefined behaviour. Reserve packing for structs that must match an external layout.

Is it safe to reorder members?

Only when the layout is yours to choose. Reordering an internal struct to save memory is free. Reordering one that maps a hardware register block, matches a wire protocol, or crosses an ABI boundary will break it — the layout is part of the contract in those cases.

Are bitfield layouts portable?

Not reliably. The C standard leaves the allocation order within a storage unit implementation defined, so a bitfield struct that decodes a register correctly on a little-endian GCC target can be wrong on a big-endian one or under MSVC. For anything crossing a boundary, shifts and masks against a plain integer are portable where bitfields are not.

Can I use a struct to parse a network packet?

It works only if you control padding, endianness, and the compiler. Casting a receive buffer to a struct pointer also violates alignment and strict aliasing rules. Reading fields explicitly with offsets and byte-order conversions is slower to write and far more portable.