Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple instances of singleton across shared libraries on Linux

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?

like image 962
bourneli Avatar asked Dec 24 '11 08:12

bourneli


People also ask

Can Singleton class have multiple instances?

Well-designed singleton can have only one instance per application. Creating of multiple instances is a mistake in the application design.

How can we avoid singleton?

If you don't want to use one of those containers, just create all instances of objects you'll need to be singleton in one place, and inject them to other objects via their constructor. You may only need one instance throughout the lifetime of your application, but you don't need the Singleton pattern for that.

Do singletons cause memory leaks?

new Singleton() does not have a matching delete , so yea, you are leaking a resource. You'll get memory back when the program closes, but not all resources are returned when the program ends.

What happens if I create more than one object for Singleton class?

The purpose of Singleton pattern is to make sure that, at any given time, there is ONE AND ONLY ONE instance of the class, within the current JVM. So, if Singleton pattern is implemented flawlessly, then, it's not at all possible to create multiple instances of a Singleton class.


1 Answers

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' 
like image 82
Employed Russian Avatar answered Sep 18 '22 18:09

Employed Russian