|
From: Niall D. <ndo...@bl...> - 2013-06-05 15:14:38
|
> On Tue, Jun 4, 2013 at 11:22 AM, Niall Douglas <ndo...@bl...> > wrote: > > This happens because until we get > > C++ Modules, we can't know the order of deinit of statically allocated > > objects. > > That is not entirely true. The order of _construction_ of static objects is not > specified, but the order of _destruction_ is guaranteed by the C++ specification > to be the opposite of the order of construction. > > This usually means that if you have arranged your code not to have dangling > pointers during construction, you will also not have segfaults during destruction. > (At the very least, it should make it straightforward to avoid the segfault you > describe.) Actually no ... the C++ spec only guarantees that for binaries loaded at the time of process init. It can't do a huge amount about binaries loaded later - and more important, here is where ELF and PE diverge in behaviour - and that's exactly what I refer to. > Also, in my experience, most C++ experts think that global static objects are > usually indicative of a poor design. They encourage excessive coupling, limit > reusability of components, and so forth. But this is just an opinion (and not > universal) so I will leave it at that. I might even call myself one of those, at least for C++ 98/03 ... I'm mentoring two Boost GSoC projects this year for example. No, the use case is quite unique: it's a static type registry, so basically it's a way of statically associating typed things with some list. The classic use case is unknown extension modules, so for example a shared library, or even just a compilation unit, declares via a static data init that some code entry point belongs to some factory of something. If you load a shared library, all its containing types get added to their registries, on unloading the shared library it all cleans up and removes from registries. There are actually a myriad of uses for static type registries, I've even used them in Boost.Spirit parsers. The problem begins with the fact that the registry can come into being any time during static init, but *has* to persist until the end of static deinit. That's fine if static data init order is consistent per shared library load/unload, and indeed it is on PE. On ELF though there's a global symbol table, and newly loaded shared objects can overwrite the symbols of earlier loaded shared objects thus hiding their storages. The problem is a static type registry *has* to export a consistent symbol mangling such that new shared libraries find it, which means that that symbol is *guaranteed* overwritten when a new shared library gets loaded. You can work around this easily using a static pointer to the static type registry, and fire and forget the allocation. What you can't so easily do is anything involving constructors and destructors, because you'll find that shared library A will do a construct and shared library B will overwrite that symbol with another construct, then you kick out library A it destructs its storage and when you come to kick out library B it's boom time. Now I've slept on it I vaguely remember a nasty hack from the mind of John Lakos I read somewhere once on how to solve this. I'll figure something out. Still, good point you made. I entirely agree with it in most other situations. Niall --- Opinions expressed here are my own and do not necessarily represent those of BlackBerry Inc. |