Quick summary of the whole filename scheme for typical shared objects (dynamically linked libraries) on typical Linux systems, as I understand it:
The actual library files are saved with a filename ending in .so.#.#.# - like libfoo.so.1.2.3 or libbar.so.0.0.0. Technically this name completely doesn't matter. The stuff after the .so is supposed to represent the version, and the major (first) version number is supposed to tell you something about ABI compatibility.
A link to the actual library is made with a filename ending with .so.# - like libfoo.so.1 or libbar.so.0. This single number will normally match the first number in the full .so.#.#.# name. This name usually matters the most! For most libraries, this is the name that other compiled binaries record inside themselves, which tells the dynamic linker what library file to look for. This name must not stay the same between ABI or API breaking changes.
Just for development, another link to the library file is made with a filename ending in just .so - like libfoo.so or libbar.so. This name normally only matters for linking at compile time, not at run time. This is the name the compiler looks up to find the library when compiling something that links against it. But the compiler does not use the link target name to figure out the name to hardcode into the binary. Instead the compiler looks inside the library file contents to find...
The SONAME - an optional field inside an already compiled shared object file. This field, if present, tells the compiler what name to actually hardcode into the binary linking to this one. Otherwise, the compiler will just use the raw .so name, without any numbers after it.
Some libraries, like glibc and musl libc, deliberately violate this convention. They just presume themselves to be special enough, and handle ABI and API incompatibility in other ways. Such as symbol versioning inside the library itself. glibc saves itself to libc.so.6 on Linux for historical reasons, because once upon a time there were five earlier versions of libc on Linux, and they were presumably ABI or API incompatible. musl libc rebels against this redundancy, and just installs itself as libc.so, no other filenames nor links, no SONAME, just libc.so.
But for the most part, libraries follow this. Or they follow a lesser convention where they don't care about incrementing the major version number when they make breaking changes. But following this convention is great, because it allows multiple incompatible library versions to coexist at the same time, which in turn allows for binaries and systems to be incrementally upgraded, instead of needing massive wholesale system updates when a central library used by many packages has a backwards incompatible update.















