Skip to content

Modern C/C++ and Toolchain ABI Hazards

The break families in Part 4 — C++ ABI Specifics predate C++11. Newer language features and toolchain flags introduce a second class of hazard: the declaration looks unchanged in the header, but the bytes the compiler emits move because a type's size, mangling, or passing rule shifted under it. These are the cases reviewers miss most often, because nothing in the diff "looks like" an ABI change.

Hazard What silently changes abicheck case
_GLIBCXX_USE_CXX11_ABI flip libstdc++ ships two std::string/std::list ABIs in parallel behind the __cxx11 inline namespace; flipping the macro re-mangles every symbol that touches those types. case104
ABI tags ([[gnu::abi_tag]]) A tag is mangled into the symbol name; adding/removing one renames the symbol with no source-visible signature change. case113
char8_t (C++20) const char*const char8_t* is a distinct type: different mangling, and a new overload-resolution result. case114
_BitInt(N) width Changing N changes size/alignment and the register/stack class the value is passed in. case115
_Atomic qualifier The representation of an _Atomic-qualified type is implementation-defined: adding/removing the qualifier can change size and alignment, and with them how the object is classified for passing/returning. case116
[[no_unique_address]] Permits an empty member to overlap the next field — so adding it can shrink the struct and shift following offsets, though existing alignment padding can absorb the overlap and leave both size and offsets unchanged. case117
Concept tightening (C++20) Narrowing a constraint removes instantiations the consumer relied on — a source break with no symbol-table change for already-emitted instantiations. case105
LP64 → ILP64 / data-model drift The library's public integer typedef widens (case112: MKL_INT intlong, 32→64-bit), so every dimension, stride, and count in the API changes width at once while the extern "C" names stay identical — the consumer links fine and silently passes integers of the wrong width. case112

Several more live only in the build flags, not the source, and abicheck surfaces them as toolchain/deployment risk when build context is captured: -fno-exceptions / -fno-rtti (drop EH/RTTI machinery callers may rely on — see Exception Unwinding for what -fno-rtti actually strips; case131 is the RTTI-mode flip), -fshort-enums (changes enum underlying size — see Part 3), packing/alignment flags, vector-ABI flags, and CPU-dispatch/IFUNC selection (case83, case29).

Which of these need debug info or headers

Most hazards above are recoverable only when DWARF/PDB or headers are supplied — they change layout, size, or a passing rule that doesn't show up in the export table alone. Two are the exception: the _GLIBCXX_USE_CXX11_ABI flip and an ABI-tag change are both mangled straight into the exported symbol name (glibcxx_dual_abi_flip_detected/abi_tag_changed, both minimum evidence L0), so they're visible from the mangled export table alone — no DWARF/PDB/headers needed. A stripped binary that has been additionally demangled (its export names rewritten to human-readable form) can still hide them, since the mangled encoding is what carries the signal; an ordinary stripped binary's raw mangled export table does not.

See also: Part 4 — C++ ABI Specifics for the core, pre-C++11 mechanisms this page's hazards sit alongside, and Exception Unwinding for the -fno-exceptions/-fno-rtti machinery in full.


Ladder:Part 4 — C++ ABI Specifics · Step 3 · How Breaks Happen · Part 5 — ELF & Linker-Level Concerns