连接器处理重复符号(二)具体不同地址的全局对象

文章目录

      • 问题
      • 例子
        • 生成静态库libsingleton.a
        • 生成动态库libfirst.so
        • 控制动态的导出符号
        • 生成动态库libsecond.so
        • 生成可执行文件app
        • 问题
      • 解决方法:

问题

在下面这个例子中,可执行程序app链接一个静态库libsingleton.a和两个动态库libfirst.solibsecond.so

  • 静态库libsingleton.a是一个单例对象,在动态库libfirst.solibsecond.so中都被链接。
  • 可执行程序app链接静态库libsingleton.a,动态库libfirst.solibsecond.so

在动态库,可执行程序中都调用了单例对象singletonDoSomething方法,它将输出单例对象的地址。最终输出的结果是每个对象的地址都不相同。这显然不符合我们的预期。就好比原本有一个封装了日志功能的静态,被多个动态库链接使用,本意是都使用同一个日志全局对象输出日志,结果缺产生了多个日志对象,这显然是不符合业务需求的。
先看下这个例子:

例子

生成静态库libsingleton.a

class Singleton {
public:
    static Singleton* GetInstance();
    static void Release();
public:
	void DoSomething(void);
private:
    Singleton(){};
    ~Singleton(){};
private:
	static Singleton* _instance;

};
#include 
#include "singleton.h"
Singleton* Singleton::_instance = nullptr;

Singleton* Singleton::GetInstance(void) {
    if (nullptr == _instance) {
        _instance = new Singleton();
    }

    return _instance;
}

void Singleton::Release() {
    if (nullptr != _instance) {
        delete _instance;
        _instance = nullptr;
    }
    
}

void Singleton::DoSomething(void) {
    std::cout<<"singleton:"<<_instance<<std::endl;
}

单例类singleton,把它编译为一个静态库。

g++ -Wall -g -fPIC -c singleton.cpp -std=c++11
ar -rcs libsingleton.a singleton.o

生成动态库libfirst.so

#ifdef __cplusplus
extern "C"
{
    void firstshlibFunction(void);
}
#endif
#include 
#include "singleton.h"
#include "firstshlib.h"
#define FUNC_EXPORT __attribute__((visibility("default")))

#ifdef __cplusplus
extern "C" {
void FUNC_EXPORT firstshlibFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
    Singleton::GetInstance()->DoSomething();
}

void firshlibOtherFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
}
}
#endif //__cplusplus

控制动态的导出符号

导出符号控制脚本,传递给链接器:

{
    global:
        firstshlibFunction;
    local:
        *;
};

使libfirst.so只导出符号firstshlibFunction

g++ -Wall -g -fPIC -I…/staticLib -c firstshlib.cpp
g++ -shared firstshlib.o -L…/staticLib -lsingleton -Wl,–version-script=versionScript -Wl,-soname,libfirst.so.1 -o libfirst.so.1.0.0

通过nm libfirst.so | grep c++filt查看libfirst.so的导出符号,如下:

0000000000000aec t Singleton::DoSomething()
0000000000000a58 t Singleton::GetInstance()
0000000000000aa0 t Singleton::Release()
0000000000202068 b Singleton::_instance
0000000000000b9a t Singleton::Singleton()
0000000000000b9a t Singleton::Singleton()
0000000000000ba4 t Singleton::~Singleton()
0000000000000ba4 t Singleton::~Singleton()
0000000000000985 T firstshlibFunction

因为libfirst.so链接了静态库libsingleton.a,它会包含静态库中的所有符号,默认情况下这些静态库都会标示为导出符号,通过连接器脚本versionScript将这些静态库符号隐藏,只导出符号firstshlibFunction

生成动态库libsecond.so

#ifdef __cplusplus
extern "C"
{
    void secondshlibFunction(void);
}
#endif

#include 
#include "singleton.h"
#ifdef __cplusplus
extern "C"
{
void secondshlibFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
    Singleton::GetInstance()->DoSomething();
}
}
#endif

编译为动态库libsecond.so,它也依赖静态库libsingleton.a,只导出符号secondshlibFunction

g++ -Wall -g -I./staticLib -I./firstshlib -I./secondshlib -c main.cpp
g++ main.o -L./staticLib -lsingleton -L./firstshlib -lfirst -L./secondshlib -lsecond -Wl,-R./firstshlib -Wl,-R./secondshlib -o app

生成可执行文件app

#include 
#include "singleton.h"
#include "firstshlib.h"
#include "secondshlib.h"
int main(void) {
    firstshlibFunction();
    secondshlibFunction();

    Singleton::GetInstance()->DoSomething();
}

可执行程序app,它也依赖静态库libsingleton.a,并且链接libfirst.solibsecond.so

g++ -Wall -g -I./staticLib -I./firstshlib -I./secondshlib -c main.cpp
g++ main.o -L./staticLib -lsingleton -L./firstshlib -lfirst -L./secondshlib -lsecond -Wl,-R ./firstshlib -Wl,-R./secondshlib -o app

问题

执行app,输出如下,全局对象的地址各不同。

firstshlibFunction
singleton:0x24f9c20
secondshlibFunction
singleton:0x24f9c40
singleton:0x24f9c60

虽然在静态库中有全局的单例对象,但是**libfirst.so****libsecond.so****app**中各自有**singleton**符号,所以全局对象地址各不相同。
这是因为没有导出**singleton**符号,所以在最终生成可执行程序**app**时,静态库中的符号作为内部符号并没有参与链接。

解决方法:

解决这种问题的方法就是,在任意一个动态库中将静态库的符号导出即可,让这些符号参与可执行文件的链接。
最优雅的方法是将这种全局功能实现为一个动态库,而非静态库。

你可能感兴趣的:(C++编译系统,c++,单例模式,开发语言)