За ABI скрывается набор правил, по которым два бинарных модуля могут работать вместе. Исполняемый файл привязан к CPU и ОС: программа для ARM не запустится на x64. Если архитектура совместима, в дело вступает ОС: формат объектного файла, представление данных, соглашение о вызовах и runtime-библиотеки.
На x64 есть два основных ABI: Windows x64 ABI и x86-64 System V ABI. Windows использует формат PE32+, System V — ELF. В Windows long занимает 32 бита, в System V — 64. Соглашение о вызовах определяет, как передаются параметры: первые целочисленные аргументы в System V идут через rdi, rsi, rdx, rcx, r8, r9, в Windows — через rcx, rdx, r8, r9, дальше стек. От уровня оптимизации это не зависит. Если пользователь добавил поле в структуру, а старая динамическая библиотека этого не знает, получается нарушение ODR.
Отсюда практические следствия. В System V структура меньше 16 байт может передаваться в регистрах, так что передача по значению быстрее, чем по ссылке. В Windows структура больше 8 байт передаётся только по ссылке, и код для обоих случаев одинаковый. unique_ptr не равен по скорости raw pointer: у него есть пользовательский copy constructor, такие объекты нельзя передавать в регистрах, только по ссылке.
Стандарт C++ не задаёт ABI, но требует, например, сохранять порядок полей в структуре. Новые версии стандарта обычно не ломают старый код, хотя C++17 сделал noexcept частью типа функции, а C++20 с no_unique_address — причина ABI break в MSVC. C++23 добавил explicit object parameter и static operator(), чтобы не передавать лишний this, но стандартные библиотеки это не используют из-за совместимости.
Реализация name mangling, виртуальных таблиц, RTTI и исключений — за компиляторами. Сейчас остались два основных C++ ABI: Itanium C++ ABI и MSVC C++ ABI. GCC и Clang на Linux используют Itanium, поэтому их бинарники совместимы. MinGW тоже использует Itanium, но работает под Windows. MSVC обещает стабильность ABI с 2015 года, GCC — с версии 3.4. Но опции вроде -fno-exceptions или -fno-rtti могут сделать библиотеки несовместимыми.
Библиотеки тоже ломаются. Переход с C++98 на C++11 сломал ABI std::string из-за отказа от COW. Программам нужен C runtime: на Windows это CRT, на Linux glibc или musl. В бинарных библиотеках нельзя менять публичные структуры — для расширения лучше оставлять зарезервированные поля вроде void* reserved. И нельзя возвращать std::vector из динамической библиотеки со статически слинкованным CRT: память выделяется в библиотеке, а освобождается в вызывающем коде, и это падает.