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:
charneeds alignment of 1 byte.intneeds 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
aoccupies byte 0.- The compiler inserts 3 bytes before
bso thatbstarts at byte 4. boccupies bytes 4–7.coccupies 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
charis a motorcycle: it can park in any one space. - An
intis 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.
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;
};
- The structure starts at offset
0. aneeds alignment 1, so it is placed at offset0. It uses 1 byte.- The next free offset is
1. bneeds an offset divisible by 4. Offset1is not valid, so the compiler inserts 3 padding bytes at offsets1,2, and3.bstarts at offset4and occupies 4 bytes: offsets4through7.cneeds alignment 1, so it is placed immediately at offset8.- The next free offset is
9. The whole structure commonly needs an alignment of 4 becauseintis its strictest member. - To make its total size a multiple of 4, 3 tail-padding bytes are added at offsets
9,10, and11. - The final size is 12 bytes.
Now trace mystruct_B:
struct mystruct_B {
int b;
char a;
};
bstarts at offset0and occupies offsets0–3.ais placed at offset4.- The next free offset is
5. - Three tail-padding bytes are added so that the structure size is a multiple of 4.
- 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
ntohsandntohl. - 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.
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, anduint32_tfrom<stdint.h>when a protocol requires fixed field widths. - Validate binary layouts: Use
_Static_assertto 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
| Term | Meaning | Typical effect |
|---|---|---|
| Alignment | A requirement or preference that an object begins at a particular address boundary | An int may need an offset divisible by 4 |
| Padding | Unused bytes inserted by the compiler to satisfy alignment rules | Makes a structure larger than the sum of its visible members |
| Tail padding | Padding after the final member | Keeps every element aligned when structures are stored in an array |
| Packing | An explicit request to reduce or remove normal padding, usually via compiler-specific features | Produces compact layouts but may create unaligned accesses |
| Structure | Typical layout with 4-byte int alignment | Typical size |
|---|---|---|
char; int; char; | 1 byte, 3 internal padding bytes, 4-byte int, 1 byte, 3 tail-padding bytes | 12 bytes |
int; char; | 4-byte int, 1 byte, 3 tail-padding bytes | 8 bytes |
int; char; char; | 4-byte int, two 1-byte fields, 2 tail-padding bytes | 8 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.
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. charcommonly has alignment 1;intcommonly 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>foroffsetof. - 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
structmemory 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.
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
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.