3 ms·
> Although a dlclose() operation is not required to remove any functions or data objects from the address space, neither is an implementation prohibited from do
by comex 7y ago
> Although a dlclose() operation is not required to remove any functions or data objects from the address space, neither is an implementation prohibited from doing so.
As far as I can tell, “neither is an implementation prohibited from doing so” does not have an exception for atexit. In other words, a POSIX compliant system is free to implement atexit in a straightforward way without any magic to check which library the function pointers come from. On such a system, if atexit is called with a function from a dlopen’d shared library, and that library is subsequently dlclose’d, the C library will cheerfully call the now-invalid function pointer at program exit, resulting in undefined behavior. As such, using atexit from libraries intended to be dlclose’d is fundamentally non-portable. Even if some system supported the “atexit keeps a library alive” behavior you’re asking for, any program relying on it would have to be very careful to ensure it only gets run on that system: unlike other non-portable features that can be tested for at compile-time or runtime, or at least fail cleanly if they’re not supported, this one would give you no warning it’s unsupported other than a segfault, if you’re lucky. And the system had better clearly document that it is guaranteeing that behavior forever, as opposed to it being an implementation detail that could change at any time. IMO, it’s a much better idea to just avoid that pattern altogether.
- zbentley 7y ago> using atexit from libraries intended to be dlclose’d is fundamentally non-portable Problem is, whether or not a library can be dlclose'd is not usually up to that library. Some things are explicitly intended as runtime-loadable plugins; others are dependencies of those plugins, and so on. Additionally, the person packaging a library for runtime linking may not be the library author. The upshot of this is basically an ass-backwards obligation (not an unfamiliar situation when working in this area): atexit is only valid for use if you know exactly how code containing it will be linked, which is impossible for a lot of code. The failure mode is worse than other similar situations (e.g. 'a library I linked clobbered my signal handler' is easier to debug and mitigate). All of that is a bit beside the point, though. We do know (and test for, and are careful about) the implementation details of atexit, and code around them, with all the benefits and drawbacks that entails. That's what this thread is about. "POSIX technically allows this function to blow up the universe when you call it" is a good thing to be mindful of, though.
- kazinator 7y agoatexit is really just for small programs; it should really be called only from the same source file where main is located. The code smell increases with increasing distance from main. If it's in an adjacent source file which is an inalienable part of that program, it's still okay.
- comex 7y ago> The upshot of this is basically an ass-backwards obligation (not an unfamiliar situation when working in this area): atexit is only valid for use if you know exactly how code containing it will be linked, which is impossible for a lot of code. True. The logical conclusion is that in most cases you just shouldn't use atexit from libraries, which... seems fine to me. In my opinion, a well-behaved library should endeavor to be cleanly unloadable without leaking any memory. (That includes using __attribute__((destructor)) if necessary; it's non-portable but without the blow-up-the-universe potential. Or just link in a C++ source file if you want portability.) That way, you can repeatedly load and unload different versions of the same library into the same process, without accumulating wasted memory over time. At least, that's useful in some cases for runtime-loadable plugins, and as you noted, other libraries may end up being loaded and unloaded as dependencies of those plugins. In fact, I'd argue that this rule of etiquette is least applicable when your library is a runtime-loadable plugin itself, but for a host that is known to never unload plugins. Admittedly, unloading libraries is fairly rare in practice, so I can forgive a library for not bothering with global destructors. But I don't think it's good practice to go out of your way to make a library not unloadable, at least without a good reason. Indeed, I'm curious what exactly the author's use case is. They say they "need to have a dependable form of post-process destruction for my tests"... what tests? Why unload libraries in a test program?