My question, as the title mentioned, is obvious, and I describe the scenario in details. There is a class named singleton implemented by singleton pattern as following, in file singleton.h:
/*
* singleton.h
*
* Created on: 2011-12-24
* Author: bourneli
*/
#ifndef SINGLETON_H_
#define SINGLETON_H_
class singleton
{
private:
singleton() {num = -1;}
static singleton* pInstance;
public:
static singleton& instance()
{
if (NULL == pInstance)
{
pInstance = new singleton();
}
return *pInstance;
}
public:
int num;
};
singleton* singleton::pInstance = NULL;
#endif /* SINGLETON_H_ */
then, there is a plugin called hello.cpp as following:
#include <iostream>
#include "singleton.h"
extern "C" 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;
}
you can see that the plugin call the singleton and change the attribute num in the singleton.
last, there is a main function use the singleton and the plugin as following:
#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("./hello.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);
}
and the makefile is following:
example1: main.cpp hello.so
$(CXX) $(CXXFLAGS) -o example1 main.cpp -ldl
hello.so: hello.cpp
$(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp
clean:
rm -f example1 hello.so
.PHONY: clean
so, what is the output? I thought there is following:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
however, the actual output is following:
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
It proves that there are two instances of the singleton class.
Why?
First, you should generally use -fPIC
flag when building shared libraries.
Not using it "works" on 32-bit Linux, but would fail on 64-bit one with an error similar to:
/usr/bin/ld: /tmp/ccUUrz9c.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
Second, your program will work as you expect after you add -rdynamic
to the link line for the main executable:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
In order to understand why -rdynamic
is required, you need to know about the way dynamic linker resolves symbols, and about the dynamic symbol table.
First, let's look at the dynamic symbol table for hello.so
:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
This tells us that there are two weak function definitions, and one global variable singleton::pInstance
that are visible to the dynamic linker.
Now let's look at the static and dynamic symbol table for the original example1
(linked without -rdynamic
):
$ nm -C example1 | grep singleton
0000000000400d0f t global constructors keyed to singleton::pInstance
0000000000400d38 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400d24 W singleton::singleton()
$ nm -C -D example1 | grep singleton
$
That's right: even though the singleton::pInstance
is present in the executable as a global variable, that symbol is not present in the dynamic symbol table, and therefore "invisible" to the dynamic linker.
Because the dynamic linker "doesn't know" that example1
already contains a definition of singleton::pInstance
, it doesn't bind that variable inside hello.so
to the existing definition (which is what you really want).
When we add -rdynamic
to the link line:
$ nm -C example1-rdynamic | grep singleton
0000000000400fdf t global constructors keyed to singleton::pInstance
0000000000401008 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400ff4 W singleton::singleton()
$ nm -C -D example1-rdynamic | grep singleton
0000000000401008 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400ff4 W singleton::singleton()
Now the definition of singleton::pInstance
inside the main executable is visible to the dynamic linker, and so it will "reuse" that definition when loading hello.so
:
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'