Case 07: Struct Layout Change¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break, API break |
Detected ChangeKinds |
type_size_changed |
| Source files | browse on GitHub |
Category: Type Layout | Verdict: ๐ก ABI CHANGE (exit 4)
Note on abidiff 2.4.0: Struct layout changes return exit 4 (not 12), but the change is semantically breaking โ all callers allocate the old size and pass wrong-length data.
What breaks¶
Code compiled against v1 allocates sizeof(Point) = 8 bytes. v2's Point is 12
bytes. Stack/heap allocations are undersized; the z field reads/writes outside the
allocated region. Any binary passing Point by value is broken without recompilation.
Why abidiff catches it¶
Reports type size changed from 64 to 96 (in bits) and 1 data member insertion.
Code diff¶
| v1.c | v2.c |
|---|---|
struct Point { int x; int y; }; |
struct Point { int x; int y; int z; }; |
Reproduce manually¶
gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 4
How to fix¶
Never add fields to public structs. Use the opaque-pointer (PIMPL) idiom: expose
struct Point* and allocate/free through library functions, so the struct layout
is hidden from callers.
Real-world example¶
The C standard library's FILE* is a classic opaque handle โ callers never see
the struct layout; all access is through fopen/fread/fclose. This pattern
keeps the ABI stable across libc versions even as the internal FILE struct changes.
Real Failure Demo¶
Severity: CRITICAL
Scenario: app allocates Point with v1 layout (8 bytes), calls init_point() from v2 which writes a z field at offset 8 โ past the allocation.
# Build v1 + app (use -O0 to ensure predictable stack layout for canary demo)
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g -O0 app.c -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ before: p={0,0} canary=0xDEADBEEF
# โ after: p={1,2} canary=0xDEADBEEF
# Swap in v2 (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ before: p={0,0} canary=0xDEADBEEF
# โ after: p={1,2} canary=0x00000003 โ CORRUPTED
# โ CORRUPTION detected! (v2 wrote past end of struct)
Why CRITICAL: The v2 library writes a z field at byte offset 8, but the app only
allocated 8 bytes for the struct. The canary variable on the stack is overwritten โ
a classic stack corruption that can corrupt control flow or cause silent data loss.
References¶
Source files¶
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.