c++ - Linux 上跨共享库的多个单例实例

标签 c++ singleton dlopen

正如标题所提到的,我的问题很明显,我详细描述了这个场景。 在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/

相关文章:

c++ - 将 c++ int 转换为 VARBINARY 并再次返回

c++ - 为什么 Ruby 没有像 C++ 一样的命名空间争议?

c++ - 为什么这个 C++11 中的 Singleton 不能正常工作?

c# - 非线程安全单例的危险是什么?

java - 如何在其他类中使用 Singleton 实例

ios - dylib 或可执行导出列表

macos - Mac OS X 应用程序可以阻止 dlopen 加载库吗?

c++ - dlopen(RTLD_NOLOAD) 在 dlclose 之后仍然返回 not null

c++ - 如何通过迭代器将 map<string, int> push_back 到 vector<map<string, int>> 中?

c++ - 如何在 C++ 代码中使用 gets 函数?