Skip to content

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

abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml

References


Source files

  • CMakeLists.txt
  • app.c
  • bad.c
  • bad.h
  • good.c
  • good.h

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.