2026-04-28
You've learned how linkers resolve symbols and how the GOT enables position-independent code. But there's a silent contract governing all of this: the Application Binary Interface (ABI). The ABI defines how compiled code interacts at the machine level — struct layout, function calling conventions, name mangling, vtable ordering, and type sizes. Break it, and binaries segfault without a single source code change.
What the ABI actually specifies:
long is 8 bytes on x86-64 Linux (LP64) but 4 bytes on x86-64 Windows (LLP64). A shared library compiled on one won't work on the other.rdi, rsi, rdx, rcx, r8, r9. ARM64 (AAPCS64) uses x0–x7. Mix them and your arguments are garbage.extern "C" to disable it.Real-world example: The infamous libstdc++ ABI break in GCC 5.1 (2015) changed std::string from a copy-on-write implementation to Small String Optimization (SSO). The internal layout of std::string changed entirely — sizeof(std::string) went from 8 bytes (a single pointer) to 32 bytes. Any library compiled with GCC 4.x that passed a std::string to code compiled with GCC 5+ would corrupt memory. The solution was the _GLIBCXX_USE_CXX11_ABI macro and years of dual-ABI support.
Rule of thumb for C struct ABI stability: you can safely append fields to a struct if callers always allocate via your API (so you control sizeof), and you version the struct with a size field. This is the pattern used by the Linux kernel's perf_event_attr — its first member is __u32 size, letting the kernel distinguish old callers from new ones. Deleting, reordering, or resizing existing fields is always an ABI break.
Checking ABI compatibility: Tools like abidiff (from libabigail) compare two shared library versions and report added/removed symbols, changed type sizes, and altered vtables. Run it in CI before releasing a library: abidiff libfoo.so.1 libfoo.so.2. Any flagged incompatibility means you must bump the SONAME.
Why C dominates system interfaces: C's ABI is simple and stable — no mangling, no vtables, no templates. That's why virtually every OS, language FFI, and plugin system defines its boundary in C, even when the implementation behind it is C++ or Rust.
