The term ABI is used to refer to two distinct but related concepts.
When talking about compilers it refers to the rules used to translate from source-level constructs to binary constructs. How big are the data types? how does the stack work? how do I pass parameters to functions? which registers should be saved by the caller vs the callee?
When talking about libraries it refers to the binary interface presented by a compiled library. This interface is the result of a number of factors including the source code of the library, the rules used by the compiler and in some cases definitions picked up from other libraries.
Changes to a library can break the ABI without breaking the API. Consider for example a library with an interface like.
void initfoo(FOO * foo)
int usefoo(FOO * foo, int bar)
void cleanupfoo(FOO * foo)
and the application programmer writes code like
int dostuffwithfoo(int bar) {
FOO foo;
initfoo(&foo);
int result = usefoo(&foo,bar)
cleanupfoo(&foo);
return result;
}
The application programmer doesn't care about the size or layout of FOO, but the application binary ends up with a hardcoded size of foo. If the library programmer adds an extra field to foo and someone uses the new library binary with the old application binary then the library may make out of bounds memory accesses.
OTOH if the library author had designed their API like.
FOO * newfoo(void)
int usefoo(FOO * foo, int bar)
void deletefoo((FOO * foo, int bar))
and the application programmer writes code like
int dostuffwithfoo(int bar) {
FOO * foo;
foo = newfoo();
int result = usefoo(foo,bar)
deletefoo(foo);
return result;
}
Then the application binary does not need to know anything about the structure of FOO, that can all be hidden inside the library. The price you pay for that though is that heap operations are involved.