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 int→long, 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 →