the application binary interface
/ A-B-I /
Two pieces of compiled code that have never met — your program and a library, or your program and the kernel — still have to agree on a great many low-level details to work together: which CPU register holds the first argument, who is responsible for saving which registers, how big an int is, how a struct is laid out in memory, how the stack is arranged. The application binary interface (ABI) is exactly that agreed-upon contract at the level of the compiled binary. If an API is the contract at the source-code level (the names and shapes of functions you call), the ABI is the contract one layer down, at the machine level, that lets the resulting binaries actually interoperate.
Concretely, the ABI pins down the things a compiler must do identically for code to link and run together. The calling convention says where arguments go (on x86-64 System V: the first six integer arguments in rdi, rsi, rdx, rcx, r8, r9, the return value in rax) and which registers a called function must preserve. It fixes data-type sizes and alignment, the memory layout of structures, the stack frame format, and how the system call is made. Because compiled programs encode these choices, they survive long after the source is gone: a binary built years ago still runs because the ABI it was built against has been kept stable.
It matters most as a stability promise. Linux famously keeps a stable kernel-to-user ABI — a program compiled decades ago can still make the same system calls today — which is why old binaries keep working across kernel upgrades. By contrast the in-kernel module interface is deliberately not stable, so a driver often must be recompiled for each kernel version. A common confusion is mixing up API and ABI: you can keep an API the same (same function names) yet break the ABI (change a struct's size), and then old binaries crash even though the source would still compile.
Two compilers building parts of the same program must agree, via the ABI, that the first integer argument arrives in rdi. If one obeyed and the other passed it on the stack instead, the called function would read garbage where the argument should be — even though both compiled cleanly and the source matched. The ABI is what stops that mismatch.
An API agrees on names; the ABI agrees on registers, sizes, and layout so binaries interoperate.
ABIs are CPU- and OS-specific: a binary built for x86-64 Linux will not run on ARM64 Linux or on Windows, because each has a different ABI even if the source is identical. This is one reason software ships in many separate builds.