I have a static C++ library that defines a singleton class. The static member variable of the singleton is a std::unique_ptr. I also have a shared library that defines a plugin which is delayed-loaded by the main application (using dlopen). Both the main application and the shared library link to the static library and make use of its singleton. All parts are compiled using compiler flags -fPIC and -rdynamic using GCC 7.5.0. The shared library and the executable are not linked at link-time.
At run-time, all components seem to make correct use of the same singleton instance. However, even though the constructor of the singleton class is only called once, its destructor is called twice, resulting in a double delete and therefore a segfault. The destructor seems to be called once for each compilation unit it is used in. If the shared library is linked to the main application at link time, this does not happen.
This issue occurred to me first when trying to use Poco::Logger from the C++ Poco library as the static library.
I looked at the question posed here and tried the example (using GCC 7.5.0) replacing the raw pointer with a std:unique_ptr. This results in the same double delete. The only way I seem to be able to prevent the double delete is to link the main application to the shared library at link-time and remove the direct link of the shared library to the static library. This would ensure only 1 copy of the shared library exists at run time. However, I wonder if that would be a good solution (besides that I don't seem to be able to do that through CMake).
Linking the shared library to the main application does not seem to make sense, since not all plugins will be known at compile time and this would defy the purpose of a plug in.
The following minimal example has been based on the example from bourneli
The static library contains the following files:
/*
* singleton.h
*
* Based on: bourneli
* Adaptation: mojoritty
*/
#ifndef SINGLETON_H_
#define SINGLETON_H_
#include <memory>
class singleton
{
private:
singleton();
static std::unique_ptr<singleton> pInstance;
public:
~singleton();
static singleton& instance();
public:
int num;
};
#endif /* SINGLETON_H_ */
and
/*
* singleton.cpp
*
* Based on: bourneli
* Adaptation: mojoritty
*/
#include "singleton.h"
#include <iostream>
singleton::singleton()
{
std::cout << "Constructed " << this << std::endl;
num = -1;
}
singleton::~singleton()
{
std::cout << "Destroyed" << this << std::endl;
}
static singleton& singleton::instance()
{
if (!pInstance)
{
pInstance.reset(new singleton());
}
return *pInstance;
}
std::unique_ptr<singleton> singleton::pInstance;
The shared library contains the following files:
// plugin.h
#include "singleton.h"
#include <iostream>
extern "C" void hello();
and
// plugin.cpp
#include "plugin.h"
void hello()
{
std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl;
++singleton::instance().num;
std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl;
}
Finally, the main application contains the following code:
/* main.cpp
*
* Author: bourneli
* Adaptation: mojoritty
*/
#include <iostream>
#include <dlfcn.h>
#include "singleton.h"
int main() {
using std::cout;
using std::cerr;
using std::endl;
singleton::instance().num = 100; // call singleton
cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton
// open the library
void* handle = dlopen("./libplugin.so", RTLD_LAZY);
if (!handle) {
cerr << "Cannot open library: " << dlerror() << '\n';
return 1;
}
// load the symbol
typedef void (*hello_t)();
// reset errors
dlerror();
hello_t hello = (hello_t) dlsym(handle, "hello");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol 'hello': " << dlerror() << '\n';
dlclose(handle);
return 1;
}
hello(); // call plugin function hello
cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton
dlclose(handle);
}
Building and running this application results in the follwing terminal output:
created 0x563018c48e70
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
destroyed 0x563018c48e70
destroyed 0x563018c48e70
free(): double free detected in tcache 2
Aborted (core dumped)