The __attribute__ keyword in GCC allows you to specify special attributes on variables, functions, and types — including struct and union definitions. It is a non-standard compiler extension that gives you fine-grained control over how the compiler handles memory layout, alignment, calling conventions, and more. This article focuses on one of its most commonly encountered uses: controlling structure packing and memory alignment.
Why Does Structure Padding Exist?
Modern CPUs do not read memory one byte at a time. Instead, they read data in word-sized chunks aligned to specific boundaries (typically 4 or 8 bytes, depending on the architecture). To accommodate this, C compilers automatically insert padding bytes between structure members so that each member starts at a naturally aligned address. This process is called structure padding.
Consider the following example:
struct Example {
char a; // 1 byte
// 3 bytes of padding inserted here
int b; // 4 bytes (must be 4-byte aligned)
char c; // 1 byte
// 3 bytes of padding at the end
};
// sizeof(struct Example) == 12, not 6
The compiler adds padding so that b is aligned to a 4-byte boundary. The result is that the struct occupies more memory than the sum of its members. This improves CPU read performance but increases memory usage.
When Is Packed Alignment Necessary?
There are situations where padded structures are undesirable or outright incorrect:
- Network and hardware protocols: Wire formats such as USB descriptors, Ethernet frames, or serial bus packets have precisely defined byte layouts. Padding would corrupt the field offsets.
- Memory-constrained embedded systems: Every byte matters on microcontrollers and similar devices.
- Binary file formats: Reading or writing binary files where the on-disk layout must match the struct layout exactly.
- Interfacing with hardware registers: Memory-mapped I/O registers require exact offsets with no gaps.
Reading or writing such data byte-by-byte is one option to avoid misaligned access exceptions, but it comes at a significant performance cost and makes the code much more verbose. Structure packing is the practical solution.
Disabling Padding: Two Common Approaches
Using #pragma pack
Most compilers support a pragma directive to control structure alignment. In GCC and Clang:
#pragma pack(1)
struct PackedExample {
char a;
int b;
char c;
};
// sizeof(struct PackedExample) == 6
#pragma pack() /* restore default alignment */
The argument to #pragma pack sets the maximum alignment of members in bytes. A value of 1 means no padding is inserted. Always remember to restore the default packing afterwards, as the pragma affects all subsequent struct definitions in the translation unit.
Using __attribute__((packed))
GCC and Clang also support the __attribute__((packed)) syntax, which applies packing directly to a single struct or union definition without affecting anything else. This is the preferred approach when you only need to pack specific types.
struct __attribute__((packed)) PackedExample {
char a;
int b;
char c;
};
Or equivalently, placed after the closing brace (common in typedef’d structs):
typedef struct {
char a;
int b;
char c;
} __attribute__((packed)) PackedExample;
Real-World Example: Apple USB CDC Driver
This pattern appears frequently in low-level system and driver code. The following is taken directly from AppleUSBCDCCommon.h in Apple’s open-source USB CDC driver:
typedef struct {
UInt8 bmRequestType;
UInt8 bNotification;
UInt16 wValue;
UInt16 wIndex;
UInt16 wLength;
} __attribute__((packed)) Notification;
This struct maps directly onto a USB CDC notification header as defined by the USB specification. Any padding between the fields would shift the offsets of wValue, wIndex, and wLength, causing the driver to misread the notification data. The __attribute__((packed)) annotation ensures the struct layout matches the wire format exactly.
A Note on Portability and Performance
__attribute__((packed)) is a GCC/Clang extension and is not part of the C standard. MSVC uses #pragma pack instead. If portability across compilers matters, use conditional compilation or stick to the pragma approach.
Also be aware that packed structs can introduce a performance penalty on some architectures. On x86 the CPU handles unaligned accesses transparently, but on ARM and other RISC architectures an unaligned access may trigger a hardware exception or require the compiler to emit slower multi-instruction sequences to load a value. Always profile and test on your target platform.