在下面这个例子中,可执行程序app
链接一个静态库libsingleton.a
和两个动态库libfirst.so
,libsecond.so
。
libsingleton.a
是一个单例对象,在动态库libfirst.so
和libsecond.so
中都被链接。app
链接静态库libsingleton.a
,动态库libfirst.so
,libsecond.so
。在动态库,可执行程序中都调用了单例对象singleton
的DoSomething
方法,它将输出单例对象的地址。最终输出的结果是每个对象的地址都不相同。这显然不符合我们的预期。就好比原本有一个封装了日志功能的静态,被多个动态库链接使用,本意是都使用同一个日志全局对象输出日志,结果缺产生了多个日志对象,这显然是不符合业务需求的。
先看下这个例子:
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
#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
。
#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
#include
#include "singleton.h"
#include "firstshlib.h"
#include "secondshlib.h"
int main(void) {
firstshlibFunction();
secondshlibFunction();
Singleton::GetInstance()->DoSomething();
}
可执行程序app
,它也依赖静态库libsingleton.a
,并且链接libfirst.so
和libsecond.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**
时,静态库中的符号作为内部符号并没有参与链接。
解决这种问题的方法就是,在任意一个动态库中将静态库的符号导出即可,让这些符号参与可执行文件的链接。
最优雅的方法是将这种全局功能实现为一个动态库,而非静态库。