This post may also thoroughly slander Linux, and if I learn why I'm an idiot and the whole problem can be solved in a much better way, hey, that's great. I'd much rather find out that there's a better way and I'm wrong than find out that things really are as broken as they seem.
Globally Symbols and Shared Libraries
Unix-style linkers (e.g. ld on both OS X and Linux) support a shared global namespace for symbols exported from shared libraries. Simply put, for any given symbol name, there can be only one 'real' implementation of that symbol, and the first dynamic library (or host app with dynamic linkage, which is basically all host apps these days) to introduce that symbol defines it for every dynamic library.In other words, if you have five implementations of "void a()" in your dynamic libraries, the first one loaded is used by everyone. It's a global namespace.
Note that if your symbol is not global, it will not be replaced by an earlier variant. So if your symbol isn't global, other people having global symbols can't hose you.
The implications of this are clear: you should be very very careful and very very minimal about what gets exported into the global namespace, because of the risk of symbol collision. I found a bug in an X-Plane plugin because the internal routine sasl_done (in a plugin called sasl) was global and the second instance loaded - sasl_done from libsasl2.dylib had already been loaded by the OS. The results: a random call into a DLL when the plugin thought it was calling itself!
Unfortunately, the default for GCC is to put everything into the global namespace. As gcc 3.x fades into history, more code is using -fvisibility=hidden and attributes more aggressively, but the defaults make it really easy to do the wrong thing and dump a whole lot of symbols into the flat namespace.
There is one exception to this global calling: if you use dlsym to resolve a symbol from a specific dynamic library (as returned by dlopen) finds it in that dynamic library, like you would expect. Therefore if you have a plugin with an "official" entry point (like "PluginStart") you can load multiple plugins into the global namespace and find the "right" start function via dlsym. (If a plugin called its own start routine, it might jump into the wrong plugin due to globla namespace issues.
What Am I Exporting?
nm my_plugin.dylib | grep "T "
Static Libraries: Not So Static
In the Unix world, the .a (static archive) format is basically a collection of .o files with some header info to optimize when the code is linked. .o files retain the hidden/visible attribute information that is used by the linker to export symbols out of a dynamic library.What this means is: under normal operation, the linker may export dynamic library symbols out of a static library you link against. In other words, if you link against libpng.a, you may end up having your DLL export all of the symbols of libpng! If you aren't the first dynamic library to load, the version of libpng you get may not be the static one you asked for.
This behavior is astonishing at best, but unfortunately it is, again, the default: if the static library didn't specifically set its symbols to hidden, you get "leakage" of static library symbols out of the client shared library. Unfortunately, from my experience this kind of leakage happens all of the time. With X-Plane we statically link libcurl, libfreetype and libpng, and all three have their symbols marked globally by default. These are ./configure based libraries and we don't want to start second-guessing their build decisions. Unfortunately the code tends to be marked up to build the right API in "shared library" mode but not static mode.
You can see this behavior using nm -m on OS X or objdump -t on Linux.