Consider a simple class of which I create only a single instance (global variable), and that has a number of static member variables, one of which (
The class dtor checks that switch to determine whether the LMDB connection has to be closed and a compression buffer deallocated.
The full code in question: https://github.com/RJVB/kdevelop/bl...ge/duchain/topducontextdynamicdata_p.cpp#L257
The code in question has been working for years on Mac (built with various clang versions) and Linux (idem, and as far as I thought also with GCC 9.4 and now GCC 12).
Out of curiosity I built the code using GCC12 on Mac, evidently
To my surprise I started getting a system report about freeing a bad pointer when closing the LMDB connection in the dtor mentioned above, which is called during the global destruction phase just before the application exists. It turns out that apparently
My question: what's the normal order of events here, and what kind of explanation can you give for the observed compiler dependency? Is my "fix" (not calling
I asked elsewhere before but got shot down because of a lacking minimal test case which I'd just as rather not write if there's an obvious error in my design. I did get the suggestion that variables are destroyed in reverse order of their creation.
My global instance is indeed created before initialising the static class members variables, so that could explain what I'm seeing with GCC but not why it doesn't happen with clang. It also seems counterintuitive: what if you access one the static members from the dtor for instance?
s_lmdbEnv
) is an instance of a C++ wrapper class of LMDB]. These members are all declared with initialisation (most to nullptr
or false
) and set properly in a dedicated init()
member function which also toggles a switch indicating that initialisation was succesfull.The class dtor checks that switch to determine whether the LMDB connection has to be closed and a compression buffer deallocated.
Code:
class LMDBHook
{
public:
~LMDBHook()
{
if (s_envExists) {
s_lmdbEnv.close();
s_envExists = false;
delete[] s_lz4CompState;
}
}
static bool init()
{
if (!s_envExists) {
s_errorString.clear();
try {
s_lmdbEnv = lmdb::env::create();
s_envExists = true;
// do some more initialisation like allocating s_lz4CompState
} catch (const lmdb::error &e) {
s_errorString = lmdbxx_exception_handler(e, QStringLiteral("database creation"));
// as per the documentation: the environment must be closed even if creation failed!
s_lmdbEnv.close();
}
}
return false;
}
static lmdb::env s_lmdbEnv;
static bool s_envExists;
static char* s_lz4CompState;
static size_t s_mapSize;
static QString s_errorString;
};
static LMDBHook LMDB;
lmdb::env LMDBHook::s_lmdbEnv{nullptr};
bool LMDBHook::s_envExists = false;
char *LMDBHook::s_lz4CompState = nullptr;
// set the initial map size to 64Mb
size_t LMDBHook::s_mapSize = 1024UL * 1024UL * 64UL;
QString LMDBHook::s_errorString;
The full code in question: https://github.com/RJVB/kdevelop/bl...ge/duchain/topducontextdynamicdata_p.cpp#L257
The code in question has been working for years on Mac (built with various clang versions) and Linux (idem, and as far as I thought also with GCC 9.4 and now GCC 12).
Out of curiosity I built the code using GCC12 on Mac, evidently
-stdlib=libc++
to avoid C++ runtime clashing.To my surprise I started getting a system report about freeing a bad pointer when closing the LMDB connection in the dtor mentioned above, which is called during the global destruction phase just before the application exists. It turns out that apparently
s_lmdbEnv
has already been destructed and either that doesn't happen with the other platform/compiler combinations or the double free is handled gracefully there. The same must happen when I use jemalloc or mimalloc. After some more specific testing it turns out that the problem can also occur on Linux.My question: what's the normal order of events here, and what kind of explanation can you give for the observed compiler dependency? Is my "fix" (not calling
s_lmdbEnv.close()
since I cannot determine whether the instance is still valid) in fact the preferred way of doing things here?I asked elsewhere before but got shot down because of a lacking minimal test case which I'd just as rather not write if there's an obvious error in my design. I did get the suggestion that variables are destroyed in reverse order of their creation.
My global instance is indeed created before initialising the static class members variables, so that could explain what I'm seeing with GCC but not why it doesn't happen with clang. It also seems counterintuitive: what if you access one the static members from the dtor for instance?