Case 56: Struct Packing Changed (pragma pack)¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break, API break |
Detected ChangeKinds |
struct_packing_changed |
| Source files | examples/case56_struct_packing_changed/ |
Category: Type Layout | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
v1's Record uses natural alignment (sizeof(Record) = 12, value at offset
4). v2 wraps the same struct in #pragma pack(push, 1), eliminating all
padding (sizeof(Record) = 6, value at offset 1). Any binary compiled
against v1 that allocates, copies, or indexes into Record reads/writes the
wrong bytes once v2's library is loaded โ a silent field-offset break, not a
crash at load time. Recompilation against v2 is mandatory.
Old/new diff¶
| bad.h (v1) | good.h (v2) |
|---|---|
typedef struct { char tag; int value; char status; } Record; (natural alignment) |
#pragma pack(push, 1) around the same fields |
sizeof(Record) = 12, value at offset 4 |
sizeof(Record) = 6, value at offset 1 |
abicheck command¶
gcc -shared -fPIC -g -include bad.h bad.c -o libfoo_v1.so
gcc -shared -fPIC -g -include good.h good.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- type_size_changed: Size changed: Record (96 -> 48 bits)
> Old code allocates or copies the type with the old size;
heap/stack corruption, out-of-bounds access.
Affected symbols: record_create, record_destroy, record_get_value
- type_field_offset_changed: Field offset changed: Record::value (32 -> 8 bits)
> Old code reads/writes fields at stale offsets; silent data corruption.
- type_field_offset_changed: Field offset changed: Record::status (64 -> 40 bits)
> Old code reads/writes fields at stale offsets; silent data corruption.
- struct_packing_changed: Struct packing added: Record is now __attribute__((packed))
> Packing attribute changed; field offsets differ from what old code expects.
Minimum evidence¶
min_evidence: L1 โ DWARF's struct-layout info (DW_AT_byte_size and each
member's DW_AT_data_member_location) is enough to detect both the size
shrink and every field's offset move; no public headers required, even
though #pragma pack is a header-level attribute โ its effect is fully
visible in the compiled layout DWARF records.
Why abicheck catches it¶
DWARF records each struct's total byte size and every member's byte offset
for both versions; abicheck diffs those directly and additionally recognizes
the packed-vs-natural alignment pattern across the two field-offset deltas as
a struct_packing_changed finding.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: app compiled against v1's natural-alignment Record (12
bytes, value at offset 4), library swapped for v2's packed Record (6
bytes, value at offset 1) without recompiling.
# Build old library + app
gcc -shared -fPIC -g -include bad.h bad.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ value = 42
# Swap in new library (no recompile)
gcc -shared -fPIC -g -include good.h good.c -o libfoo.so
./app
# โ value = 22528
# โ WRONG RESULT: struct packing/layout changed
Why CRITICAL: the app's own Record layout (compiled from bad.h)
still expects value at offset 4, but v2's record_create writes value
at offset 1 into a 6-byte allocation the app never resized. The read at
offset 4 lands on bytes that used to be padding/status, producing a
silently wrong value instead of a crash.
Safe redesign¶
Never change a public struct's packing/alignment after release โ it's an
ABI-visible layout change even though the struct definition still
"looks the same" at the field-list level. Use the opaque-pointer (PIMPL)
idiom instead: expose Record* and let the library allocate/free it, so
callers never embed the struct's size or layout in their own code.
Real-world example: Windows API headers use #pragma pack extensively;
mixing packed and unpacked struct definitions across DLL boundaries (e.g. a
stale cached header on one side of the link) is a recurring source of
silent data corruption in interop code.
Cross-tool comparison¶
References¶
Source files¶
CMakeLists.txtapp.cbad.cbad.hgood.cgood.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.