I think a lot of the issues of "dependency hell" come from the non-obvious way to specify a specific version of a shared library to link against (in fact, I can't even find out how to do this in the documentation for gcc or ld now, but I know I've done it, and that it's possible).

Take libncurses for example; I'm using it as an example because there was a time when multiple versions were installed on many linux distros. Both ncurses4 and ncurses5 could be available, and in fact, this used to be case with a lot of linux distros, ncurses4 was often installed for binary compatibility with stuff that was previously compiled.

So you end up with the following files on your machine:

libncurses.so.5.7
libncurses.so.5 (symlink to .5.7, maintained by ldconfig)
libncurses.so (symlink to .5.7, mainted by ldconfig)

Ideally, you'd be able to install ncureses4 on the same machine, and you'd end up with these files also:

libncurses.so.4.x
libncurses.so.4 (symlink to .4.x, maintained by ldconfig)

the .so entry is a link, maintained by ldconfig, to the latest version. There is no conflict because shared linkage is against a specific A.B.C version, resolved at runtime (see below).

So if you compile/link with -lncurses, you get the latest one (which is a reasonable default), as that's what the .so points to. But if you have ncurses4 installed, and you know you want to link against that API, you need to link against the specific file (with a full path and version number in it), rather than use ld.so.cache and gain the advantages of runtime library location resolution.

So the work around for the inability to specify an exact version number to link against without having to give the full path to the library is to move some of the version number to before the .so and make it part of the library name. So you now you link with -llibraryX-2.4 which looks for liblibraryX-2.4.so. No one calls this, or thinks of it as, libraryX-2.4, they think of it as libraryX. Since there is no standard way to do name or indicate version numbers of with this work around, you sometimes end up with these different formats:

libXXX.so.1.2.3
libXXX-1.so.2.3
libXXX-1.2.so.3
libXXX-1.2.so.1.2.3
libXXX-1.2.3.so

Each library maintainer making their own version number format. This could all be the exact same library version.

A quick demo to show that when using -llibrary (and -shared), the full path isn't stored in the binary, just the filename and version number, which is resolved at runtime by the dynamic linker in ld.so to an actual full path using the cache created by ldconfig (hardly conclusive, but you get the gist -- could also use binutils stuff here to see this):

$ strings /etc/ld.so.cache | grep libmagic
libmagic.so.1
/usr/lib64/libmagic.so.1
$ ldd `which rpm` | grep magic
libmagic.so.1 => /usr/lib64/libmagic.so.1 (0x00007f9dfed95000)
$ strings `which rpm` | grep libmagic
libmagic.so.1

In this case, we see that ld.so.cache maintains a library name to filename mapping, rpm mentions just the library name (with version number), and ldd resolves that to a full path, and that there is no mention of the full path in the rpm binary itself.

I'd really like to see a -llibrary=version option that helps with this, allowing a specific A, A.B, or A.B.C (or whatever string is after .so in the filename)... maybe I was thinking of some linker other than gnu ld that works like this.