在Linux上的共享库中的单例的多个实例 [英] Multiple instances of singleton across shared libraries on Linux

查看:211
本文介绍了在Linux上的共享库中的单例的多个实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题,正如标题所提到的,是显而易见的,我详细描述场景。
在singleton.h文件中有一个名为singleton的单例模式实现的类:

  / * 
* singleton.h
*
*创建日期:2011-12-24
*作者: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> 
#includesingleton.h

externCvoid 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。



最后,有一个main函数使用单例和插件如下:

  #include< iostream> 
#include< dlfcn.h>
#includesingleton.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

//打开库
void * handle = dlopen(./ hello.so,RTLD_LAZY);

if(!handle){
cerr<< 无法打开库:< dlerror()<< '\\\
';
return 1;
}

//加载符号
typedef void(* hello_t)();

// reset errors
dlerror();
hello_t hello =(hello_t)dlsym(handle,hello);
const char * dlsym_error = dlerror();
if(dlsym_error){
cerr<< 无法加载符号'hello':<< dlerror()<< '\\\
';
dlclose(handle);
return 1;
}

hello(); // call plugin function hello

cout<< singleton.num in main:< singleton :: instance()。num<< endl; // call singleton
dlclose(handle);
}

,makefile如下:

  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



这证明单例类有两个实例。



p>

解决方案

首先,在构建共享库时,通常应使用 -fPIC 标志。



在32位Linux上不使用工作,但在64位操作系统上失败,错误类似于:


$在创建共享对象时,不能使用针对`.rodata'的重定位R_X86_64_32 b $ b

  / usr / bin / ld:/tmp/ccUUrz9c.o:重新编译-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 ,你需要知道动态链接器解析符号和关于动态符号表。



首先,让我们看看 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全局构造函数keyed to singleton :: pInstance
0000000000400d38 W singleton :: instance()
00000000006022e0 B singleton :: pInstance
0000000000400d24 W singleton :: singleton

$ nm -C -D example1 | grep singleton
$

这是正确的:即使 :pInstance 作为全局变量存在于可执行文件中,该符号不存在于动态符号表中,因此对动态链接器不可见。



因为动态链接器不知道 example1 已经包含 singleton的定义: :pInstance ,它不会将 hello.so 中的那个变量绑定到现有的定义(这是你真正想要的)。



当在链接行中添加 -rdynamic 时:

  $ nm -C example1-rdynamic | grep singleton 
0000000000400fdf t全局构造函数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 的定义是 visible 动态链接器,因此在加载 hello.so 时将重用该定义:

  LD_DEBUG = bindings ./example1-rdynamic |& grep pInstance 
31972:绑定文件./hello.so [0]到./example1-rdynamic [0]:正常符号`_ZN9singleton9pInstanceE'


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'

这篇关于在Linux上的共享库中的单例的多个实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆