Struct padding calculator
Paste a C struct to see how a compiler lays it out in memory: the offset of every member, each byte of padding and why it is there, the total sizeof, and the same members in an order that wastes less.
The last struct or union is laid out; earlier ones can be used as member types. Understands the basic types, <stdint.h> types, pointers, arrays, nested structs and unions, typedefs, #pragma pack, packed and _Alignas. Bit-fields are not supported.
sizeof(struct packet) is 32 bytes on x86-64 Linux and macOS, 16 of them padding.
Byte by byte
Each row is 8 bytes. Members are labelled in their colour, with their number from the table where a name does not fit; hatched cells are padding, and hatching in a member's colour is padding inside that member.
Members
| Member | Type | Offset | Size | Align | Padding before |
|---|---|---|---|---|---|
| tagchar | char | 0 | 1 | 1 | 0 |
| valuedouble | double | 8 | 8 | 8 | 7 |
| idshort | short | 16 | 2 | 2 | 0 |
| countint | int | 20 | 4 | 4 | 2 |
| flagchar | char | 24 | 1 | 1 | 0 |
| Trailing padding, to a multiple of 8 | 7 | ||||
Why each member is where it is
- tag (char, 1 byte, alignment 1): the first member always starts at offset 0.
- value (double, 8 bytes, alignment 8): offset 1 is not a multiple of 8, so 7 bytes of padding go in first and it starts at 8.
- id (short, 2 bytes, alignment 2): offset 16 is already a multiple of 2, so it starts right there.
- count (int, 4 bytes, alignment 4): offset 18 is not a multiple of 4, so 2 bytes of padding go in first and it starts at 20.
- flag (char, 1 byte, alignment 1): offset 24 is already a multiple of 1, so it starts right there.
- The members end at 25. The struct's alignment is 8, its largest member alignment, so the size rounds up to 32 with 7 bytes of trailing padding.
Reordered: 16 bytes
The same members sorted by alignment, largest first (32 to 16 bytes, 0 of padding). Any struct defined inside it is written out first. Reordering changes the binary layout, so only do it where nothing depends on the old one.
struct packet {
double value;
int count;
short id;
char tag;
char flag;
};
offsetof checks to paste into your code
These fail the build if the layout ever changes, for example after someone adds a member or a compiler flag changes packing. They hold for x86-64 Linux and macOS only.
/* Layout on x86-64 Linux and macOS (x86_64-linux-gnu) */ #include <stddef.h> _Static_assert(sizeof(struct packet) == 32, "size"); _Static_assert(offsetof(struct packet, tag) == 0, "tag"); _Static_assert(offsetof(struct packet, value) == 8, "value"); _Static_assert(offsetof(struct packet, id) == 16, "id"); _Static_assert(offsetof(struct packet, count) == 20, "count"); _Static_assert(offsetof(struct packet, flag) == 24, "flag");
On every target
| Target | sizeof | Align | Padding |
|---|---|---|---|
| 32 | 8 | 16 | |
| 32 | 8 | 16 | |
| 24 | 4 | 8 | |
| 32 | 8 | 16 | |
| 32 | 8 | 16 |
How a compiler lays out a struct
Every type has an alignment: the number its address has to be a multiple of. On x86-64 Linux and macOS,
char has alignment 1, short 2, int
and float 4, and double, long and pointers
8. Two rules then decide the whole layout.
- Each member starts at the next offset that is a multiple of its alignment. The first member is always at offset 0, and members stay in the order you wrote them. If the previous member ended at an offset the next one cannot use, the compiler fills the gap with padding bytes.
- The size is a multiple of the largest member alignment. That largest alignment becomes the struct's own, and the compiler pads the end to reach it. In an array the elements are sizeof apart, so this is what keeps the members of the second, third and later elements aligned too.
Alignment exists because processors fetch memory in aligned chunks. A 4-byte int at an address that is a multiple of 4 never straddles two chunks, so one load reads it. Some processors handle a misaligned load in hardware at a cost; others refuse it outright. The ABI for each platform fixes every type's alignment so that separately compiled code agrees on where each member is.
Worked example: struct packet
The default struct on x86-64, member by member. It holds 16 bytes of data in 32 bytes.
struct packet {
char tag;
double value;
short id;
int count;
char flag;
};
- tag (char, 1 byte, alignment 1): the first member always starts at offset 0.
- value (double, 8 bytes, alignment 8): offset 1 is not a multiple of 8, so 7 bytes of padding go in first and it starts at 8.
- id (short, 2 bytes, alignment 2): offset 16 is already a multiple of 2, so it starts right there.
- count (int, 4 bytes, alignment 4): offset 18 is not a multiple of 4, so 2 bytes of padding go in first and it starts at 20.
- flag (char, 1 byte, alignment 1): offset 24 is already a multiple of 1, so it starts right there.
- The members end at 25. The struct's alignment is 8, its largest member alignment, so the size rounds up to 32 with 7 bytes of trailing padding.
So 16 of the 32 bytes, 50%, are padding. On 32-bit x86 Linux, where double only needs 4-byte alignment inside a struct, the same struct is 24 bytes.
Reordering members to remove padding
A C compiler never reorders members, because the standard requires their addresses to increase in declaration order. You can. Sort the members by alignment, largest first, and each one starts exactly where the previous one ended: a type's size is always a multiple of its alignment, so after the 8-byte members the offset is a multiple of 8, after the 4-byte ones a multiple of 4, and so on down.
struct packet {
double value;
int count;
short id;
char tag;
char flag;
};
That is 16 bytes instead of 32, with
0 bytes of padding. For one struct the saving hardly matters; for a million
of them in an array it is 16 MB, and more of them fit in each cache
line. The one case the rule does not cover is a member with a raised _Alignas, which can
be larger than its size; the calculator still computes the result exactly. A struct defined inside another with a
tag is written out ahead of it, since C gives that tag file scope anyway.
Packing: #pragma pack and the packed attribute
Packing tells the compiler to lower alignment, so it inserts less padding or none. It is meant for data whose layout is fixed from outside the program: file headers, network packets and hardware registers.
#pragma pack(n)caps every member's alignment at n (1, 2, 4, 8 or 16) for the structs defined after it.#pragma pack(push, n)and#pragma pack(pop)save and restore the previous value;#pragma pack()goes back to the default. GCC, clang and MSVC all accept it.__attribute__((packed))is the GCC and clang spelling for alignment 1 on one struct. MSVC does not have it, so code that must build there uses#pragma pack.
The file header of a BMP image is 14 bytes on disk. As a struct it needs
#pragma pack(1) to match: without it, the compiler pads the 2-byte magic number so the
4-byte size starts at offset 4, and the struct is 16
bytes. The cost is that members end up misaligned. x86-64 and AArch64 load misaligned values in hardware, sometimes
more slowly; on processors that cannot, such as the Cortex-M0, the compiler has to read packed members a byte at a
time. A pointer to a packed member may itself be misaligned, which is why GCC 9 and later and clang warn about taking
one (-Waddress-of-packed-member).
_Alignas goes the other way and raises a member's alignment. The usual reason is to keep
two counters that different threads update on separate 64-byte cache lines, so they do not keep invalidating each
other: struct counters with alignas(64) on both members is
128 bytes, not 16. GCC and clang let #pragma pack lower an
_Alignas as well; MSVC keeps the _Alignas. The calculator
follows each.
Sizes and alignments on each target
Size / alignment in bytes, as members of a struct. Every value was checked against clang for the target named in each column.
| Type | x86-64 SysVLP64 | x86-64 MSVCLLP64 | i386ILP32 | ARM32ILP32 | AArch64LP64 |
|---|---|---|---|---|---|
| char | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 |
| _Bool / bool | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 |
| short | 2 / 2 | 2 / 2 | 2 / 2 | 2 / 2 | 2 / 2 |
| int | 4 / 4 | 4 / 4 | 4 / 4 | 4 / 4 | 4 / 4 |
| long | 8 / 8 | 4 / 4 | 4 / 4 | 4 / 4 | 8 / 8 |
| long long | 8 / 8 | 8 / 8 | 8 / 4 | 8 / 8 | 8 / 8 |
| float | 4 / 4 | 4 / 4 | 4 / 4 | 4 / 4 | 4 / 4 |
| double | 8 / 8 | 8 / 8 | 8 / 4 | 8 / 8 | 8 / 8 |
| long double | 16 / 16 | 8 / 8 | 12 / 4 | 8 / 8 | 16 / 16 |
| pointer, size_t | 8 / 8 | 8 / 8 | 4 / 4 | 4 / 4 | 8 / 8 |
| int64_t | 8 / 8 | 8 / 8 | 8 / 4 | 8 / 8 | 8 / 8 |
| wchar_t | 4 / 4 | 2 / 2 | 4 / 4 | 4 / 4 | 4 / 4 |
5 target columns: scroll the table sideways to see them all.
The columns are x86-64 SysV: x86-64 Linux and macOS (x86_64-linux-gnu); x86-64 MSVC: x86-64 Windows (MSVC) (x86_64-pc-windows-msvc); i386: 32-bit x86 Linux (i386-linux-gnu); ARM32: 32-bit ARM (AAPCS) (armv7-linux-gnueabihf); AArch64: AArch64 (64-bit ARM) Linux (aarch64-linux-gnu). Values that differ from x86-64 Linux are highlighted. The MSVC column is clang's Windows target, which follows MSVC's layout rules. For the range each integer width can hold, see the integer limits tables.
The struct node example shows how much pointer size matters:
- x86-64 Linux and macOS: 24 bytes
- x86-64 Windows (MSVC): 24 bytes
- 32-bit x86 Linux: 12 bytes
- 32-bit ARM (AAPCS): 12 bytes
- AArch64 (64-bit ARM) Linux: 24 bytes
And struct record, with a long between a
char and a long long, is 16 bytes on Windows
and 24 on x86-64 Linux, because long is 4 bytes on one and 8 on the
other.
Common mistakes
- Writing a struct straight to a file or socket. Its padding, byte order and type sizes are those of the machine that wrote it. Another compiler or platform can read it back wrongly. Serialise field by field, or pack the struct and use fixed-width types and a fixed byte order.
- Comparing structs with
memcmp. The C standard leaves the value of padding bytes unspecified, so two structs with equal members can still differ byte for byte. Compare member by member. - Leaking memory through padding. Copying a struct out of a program, for example from a kernel to
a user, copies its padding too, along with whatever was in memory there. Zero the struct with
memsetfirst. - Allocating the sum of the members for a flexible array. A member written
data[]has no size of its own, but its alignment still counts. Instruct samplesthe other members take 3 bytes, yetdatastarts at offset 4, after 1 byte of padding, and sizeof is 4. Allocateoffsetof(struct samples, data)or sizeof plus the array, never the sum of the other members. - Packing everything. A packed struct saves bytes but turns ordinary loads into misaligned ones. Reorder first; pack only what has to match an outside format.
To read the bytes of a dump as numbers, the hex to decimal converter and the binary converter help, and the file signature checker reads the first bytes of a file header. Masks and shifts for packed flags are on the bit manipulation tricks page.
Questions
Why is sizeof my struct bigger than the sum of its members?
Because the compiler adds padding so that each member sits at an address that is a multiple of its alignment, and rounds the total up to a multiple of the largest alignment. The struct packet example holds 16 bytes of data but is 32 bytes on x86-64: 16 bytes, 50%, are padding.
How do I reduce struct padding?
Order the members from the largest alignment to the smallest: 8-byte members (double, pointers, long long) first, then 4, 2 and 1. Sorted that way, struct packet shrinks from 32 to 16 bytes on x86-64 without losing anything. Packing removes padding too, but it makes members misaligned, which costs speed or correctness on some processors.
Does the compiler reorder struct members to save space?
No. C requires members to have increasing addresses in the order they are declared, so a C compiler never reorders them. It can only add padding. Reordering is up to you, and it changes the binary layout, so it breaks any file format or library interface that depends on the old one.
What does #pragma pack(1) do?
It caps every member's alignment at 1 byte, so the compiler adds no padding at all, unless a member has _Alignas, which MSVC keeps even under the pragma. The file header at the start of a BMP image, written as a struct, is 14 bytes packed, matching the file, and 16 bytes without the pragma. #pragma pack(n) with n of 2, 4, 8 or 16 caps alignment at n instead, and #pragma pack(push, n) and #pragma pack(pop) save and restore the previous setting.
Is the struct layout the same on every platform?
No. Type sizes and alignments are set by each platform's ABI. long is 8 bytes on 64-bit Linux and macOS but 4 on 64-bit Windows, and 32-bit x86 Linux aligns double and long long to only 4 bytes inside a struct, so struct packet is 24 bytes there and 32 on x86-64. Use fixed-width types and explicit serialisation for anything that leaves the program.
Why does a struct have padding at the end?
For arrays. Array elements sit back to back, sizeof apart, so the size must be a multiple of the struct’s alignment for every element’s members to stay aligned. A struct holding a double and a char is 16 bytes on x86-64, not 9, so the double in the second element of an array still starts at a multiple of 8.