Case 26b: Union Field Added (No Size Change)¶
| Field | Value |
|---|---|
| Verdict | ๐ข COMPATIBLE |
| Category | Addition (Compatible) |
| Platforms | Linux, macOS, Windows |
| Flags | โ |
Detected ChangeKinds |
union_field_added |
| Source files | examples/case26b_union_field_added_compatible/ |
Category: Type Layout | Verdict: ๐ข COMPATIBLE
Verdict and consumer impact¶
union Value gains an int i member, but int (4 bytes) is smaller than
the union's existing largest member, double d (8 bytes). sizeof(union
Value) stays 8 bytes in both versions, every existing member keeps its
offset-0 layout, and fill() still writes through v->l in both
versions. A caller compiled against v1 needs no recompilation โ its
allocations are already the right size for v2.
Old/new diff¶
| old/lib.h | new/lib.h |
|---|---|
union Value { long l; double d; }; |
union Value { long l; double d; int i; }; |
abicheck command¶
gcc -shared -fPIC -g old/lib.c -Iold -o libval_v1.so
gcc -shared -fPIC -g new/lib.c -Inew -o libval_v2.so
abicheck compare libval_v1.so libval_v2.so
Expected abicheck finding¶
Verdict: COMPATIBLE (exit 0)
Additions:
- union_field_added: Union field added: Value::i (int)
> Union size may grow; old code allocating with old sizeof gets
truncated data.
Affected symbols: fill
Minimum evidence¶
min_evidence: L1 โ DWARF's DW_TAG_union_type carries both the new
member and the union's total byte size; abicheck sees the new member (via
the member list) but reports no type_size_changed, because DWARF's size
field is identical (8 bytes) in both versions. No public headers required.
Why abicheck catches it¶
abicheck diffs the two unions' member lists from debug info the same way
it does in case26, finding i as a new member โ but it separately
compares the DWARF-reported total size, which is unchanged here (max(8,
8, 4) == 8). A field addition alone, with no size change, cannot
under-allocate a caller, so it's classified as a compatible addition
rather than type_size_changed.
Runtime failure demonstration¶
No observable effect on existing binaries โ fill() still writes through
v->l in both versions, and sizeof(union Value) is unchanged, so an app
built against v1 behaves identically against v2 with no recompilation.
# Build old library + app
gcc -shared -fPIC -g old/lib.c -Iold -o libval.so
gcc -g app.c -Iold -L. -lval -Wl,-rpath,. -o app
./app
# โ after fill: v.l = 42
# โ OK: union field added (no size change) is ABI-compatible
# Swap in new library (no recompile)
gcc -shared -fPIC -g new/lib.c -Inew -o libval.so
./app
# โ after fill: v.l = 42
# โ OK: union field added (no size change) is ABI-compatible (identical)
Safe redesign¶
This is already the safe way to extend a union: only add members whose
size is no larger than the union's current largest member, so sizeof()
never changes. Contrast with case26 (union_field_added, BREAKING), where
the new member is the largest and grows the union.
Cross-tool comparison¶
References¶
Source files¶
CMakeLists.txtapp.c
See also: Examples overview ยท All COMPATIBLE cases ยท Category: Addition (Compatible).