Case 42: Type Alignment Changed (standalone alignas)¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break, API break |
Detected ChangeKinds |
type_alignment_changed |
| Source files | examples/case42_type_alignment_changed/ |
Category: Type Layout | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
v1 defines CacheBlock with __attribute__((aligned(8))). v2 increases the
alignment requirement to aligned(64) for cache-line optimization. The
fields and their types are identical โ only the alignment attribute
changes. Old binaries allocate CacheBlock (on the stack, in arrays, via
malloc) with 8-byte alignment guarantees; a v2 library built to assume
64-byte alignment (e.g. using aligned SIMD loads/stores) can fault on
strict-alignment architectures, and violates the alignment contract even
where it doesn't immediately crash.
Old/new diff¶
| bad.h (v1) | good.h (v2) |
|---|---|
typedef struct __attribute__((aligned(8))) { char data[56]; long checksum; } CacheBlock; |
typedef struct __attribute__((aligned(64))) { char data[56]; long checksum; } CacheBlock; |
abicheck command¶
gcc -shared -fPIC -g bad.c -include bad.h -o libfoo_v1.so
gcc -shared -fPIC -g good.c -include good.h -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- type_alignment_changed: Alignment changed: CacheBlock (64 -> 512 bits)
> Misaligned access can cause bus errors on strict architectures or
silent data corruption with SIMD.
Affected symbols: block_checksum, block_init, block_process
Minimum evidence¶
min_evidence: L1 โ DWARF's DW_AT_alignment attribute (or the alignment
inferred from DW_AT_byte_size padding) records CacheBlock's required
alignment for both versions; abicheck reads this straight from debug info
compiled with -g, no public headers required.
Why abicheck catches it¶
abicheck's DWARF-based type differ reads each struct's alignment attribute
(or the alignment implied by its padded size) from DW_TAG_structure_type
and compares it across versions, independent of whether any field or
field-offset actually moved โ an alignment-only change like this one would
be invisible to a diff that only looks at member offsets and total size.
Runtime failure demonstration¶
Severity: BREAKING (alignment contract violation; effect is architecture-dependent).
# Build v1 library + app (app is compiled against v1's aligned(8) layout)
gcc -shared -fPIC -g bad.c -include bad.h -o libv1.so
gcc -g app.c -L. -lv1 -Wl,-rpath,. -o app_v1
./app_v1
# โ block_process = 4 (expected 4)
# โ OK on this arch (BREAKING in strict sense: alignment ABI changed)
# Swap in v2 library (no recompile)
gcc -shared -fPIC -g good.c -include good.h -o libv1.so
./app_v1
# โ block_process = 4 (expected 4)
# โ OK on this arch (BREAKING in strict sense: alignment ABI changed)
Why this doesn't crash here, and why it's still BREAKING: x86-64's
memory subsystem tolerates most misaligned accesses transparently (at a
performance cost, or none at all for char/long-only fields like this
one), so this exact test does not observably fail on this architecture โ
consistent with app.c's own comment. The alignment mismatch is still
undefined behavior per the C standard, and the same scenario reliably
faults with SIGBUS on strict-alignment architectures (ARM without
unaligned-access support, RISC-V) or with any code path that relies on the
alignment guarantee (aligned SIMD loads/stores against CacheBlock
instances). abicheck reports it as BREAKING because the alignment contract
changed, not because every possible caller crashes on every architecture.
Safe redesign¶
Use opaque types so callers never allocate or embed the struct directly โ the library, not the caller, then controls alignment:
/* header */
typedef struct CacheBlock CacheBlock;
CacheBlock* block_alloc(void); /* library controls alignment */
/* implementation */
struct CacheBlock __attribute__((aligned(64))) { ... };
CacheBlock* block_alloc(void) {
return aligned_alloc(64, sizeof(CacheBlock));
}
Real-world example: DPDK packet buffers require cache-line alignment
(64 bytes); Intel TBB / oneTBB uses alignas(64) for scalable allocator
metadata. Changing alignment in a public struct after release has broken
ABI in several multimedia libraries (FFmpeg, GStreamer) for exactly this
reason.
Cross-tool comparison¶
References¶
Source files¶
CMakeLists.txtapp.cbad.cbad.hgood.cgood.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.