正如标题所提到的,我的问题很明显,我详细描述了这个场景。 在singleton.h文件中有一个由单例模式实现的名为singleton的类,如下所示:
/*
* 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_ */
然后,有一个名为 hello.cpp 的插件如下:
#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;
}
可以看到插件调用了singleton并改变了singleton中的属性num。
最后,使用单例和插件的主要功能如下:
#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);
}
生成文件如下:
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
那么,输出是什么? 我认为有以下几点:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
但是,实际输出如下:
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
证明单例类有两个实例。
为什么?
最佳答案
首先,在构建共享库时,通常应该使用 -fPIC
标志。
在 32 位 Linux 上不使用它“有效”,但在 64 位 Linux 上会失败,并出现类似以下错误:
/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
其次,将 -rdynamic
添加到主可执行文件的链接行后,您的程序将按预期运行:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
为了理解为什么需要-rdynamic
,你需要了解动态链接器解析符号的方式,以及dynamic符号表。
首先我们看一下hello.so
的动态符号表:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
这告诉我们有两个弱函数定义和一个全局变量 singleton::pInstance
对动态链接器可见。
现在让我们看一下原始example1
(不带-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
$
没错:即使 singleton::pInstance
作为全局变量存在于可执行文件中,该符号也不存在于 dynamic 符号表中,因此对动态链接器“不可见”。
因为动态链接器“不知道” example1
已经包含 singleton::pInstance
的定义,所以它不会在 内绑定(bind)该变量hello.so
到现有的定义(这是你真正想要的)。
当我们将 -rdynamic
添加到链接行时:
$ 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()
现在主可执行文件中 singleton::pInstance
的定义对动态链接器是可见的,因此它会在加载 时“重用”该定义hello.so
:
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
关于c++ - Linux 上跨共享库的多个单例实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8623657/