Linux C++ 动态库和静态初始化顺序
Linux C++ Dynamic Libs and static initialization order
见长见谅post。这是一个复杂的问题,我想要一个完整的描述。
在 Linux Fedora 21 (g++ 4.9.2) 等上。
我正在使用 "database" 的基础 class 并继承 Oracle 和 Sybase 的 classes 开发 C++ 数据库包装器库。
除此之外,我希望某些程序能够使用 dlopen 在 运行 时动态加载库,遵循 http://www.linuxjournal.com/article/3687?page=0,0 和其他人的模板。
其他程序直接link他们需要的版本。我的解决方案的问题是 database.cpp 中存在静态 std::map(请参阅下面的代码),必须在将其分配给 oracle / sybase.cpp 的静态初始化程序之前对其进行初始化。我能够做到这一点的唯一方法是在编译时通过 -l 参数的物理顺序(参见 Makefile)。 让它们正确:,程序 运行 没问题。 向后获取它们: -- 程序编译并 links 找到,但会在执行时立即崩溃。这让我很困扰,因为我更喜欢成功的 compile/link 来产生成功的 运行 而图书馆顺序的看似翻转通常不会这样做。我知道 link 命令导致 link 错误的情况并不少见,但不会导致 运行 时间错误。
两个问题:
- 除了库顺序之外,我可以使用其他一些 link 选项来确保正确初始化吗?
- 谁能看到另一种算法可以消除这种依赖性。 (我不希望普通程序必须声明 DBFactory,它需要留在库中,但 main_dlopen 中的额外步骤就可以了)。
这是代码、所有示例模块和 Makefile。包括 2 个主要程序,一个用于正常 linking (main_direct) 和一个用于 运行time linking (main_dlopen)。两者都将按照给定的方式编译和运行(除非有剪切粘贴错字)。
感谢您的关注...
// database.h
#include <map>
#include <string>
#include <iostream>
#include <stdio.h>
class Database; // forward declaration
typedef Database* maker_t();
// our global factory
typedef std::map<std::string, maker_t *> DBFactory_t;
extern DBFactory_t DBFactory;
class Database
{
public:
Database () {}
virtual ~Database () {};
};
// database.cpp
#include "database.h"
__attribute__((constructor)) void fooDatabase (void) {
fprintf (stderr, "Database library loaded\n");
}
// our global factory for making dynamic loading possible
// this is the problem child static global
std::map<std::string, maker_t * > DBFactory ;
// oracle.h
#include "database.h"
class Oracle : public Database
{
public:
Oracle ();
virtual ~Oracle ();
};
// oracle.cpp class.
#include "oracle.h"
using namespace std;
__attribute__((constructor)) void fooOracle (void) {
fprintf (stderr, "Oracle library loaded\n");
}
// the following code follows the referece at
// http://www.linuxjournal.com/article/3687?page=0,0
extern "C" {
Database * Maker()
{
return new Oracle;
}
class proxy {
public:
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Oracle Proxy Constructor\n");
DBFactory["ORACLE"] = Maker;
}
};
}
proxy p;
Oracle::Oracle () {
cout << "Oracle Constructor" << endl;
}
Oracle::~Oracle ()
{
cout << "Oracle Destructor" << endl;
}
// sybase.h
#include "database.h"
class Sybase : public Database
{
public:
Sybase ();
virtual ~Sybase();
};
// sybase.cpp class.
#include "sybase.h"
using namespace std;
__attribute__((constructor)) void fooSybase (void) {
fprintf (stderr, "Sybase library loaded\n");
}
extern "C" {
Database * Maker()
{
return new Sybase;
}
class proxy {
public:
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Sybase Proxy Constructor\n");
DBFactory["SYBASE"] = Maker;
}
};
}
proxy p;
Sybase::Sybase () {
cout << "Sybase Constructor" << endl;
}
Sybase::~Sybase ()
{
cout << "Sybase Destructor" << endl;
}
// main_direct.cpp
#include "oracle.h"
int main ()
{
Oracle db ();
return 0;
}
// main_dlopen.cpp
#include <iostream>
#include <dlfcn.h>
#include <stdlib.h>
#include "database.h"
using namespace std;
int main ()
{
void * dl = dlopen ("libSybase.so", RTLD_NOW);
if (!dl) { cerr << dlerror() << endl; exit (1); }
Database * db = DBFactory["SYBASE"] ();
delete db;
return 0;
}
#Makefile
CXXFLAGS = -Wall -fPIC -ggdb -std=c++11
all: main libOracle.so libSybase.so libdb.so
main: main_dlopen main_direct
main_dlopen: main_dlopen.o libdb.so libOracle.so libSybase.so
${CXX} -o main_dlopen main_dlopen.o -L. -ldb -ldl
# reverse -lOracle and -ldb to make this program crash
main_direct: main_direct.o libdb.so libOracle.so libSybase.so
${CXX} -o main_direct main_direct.o -L. -lOracle -ldb
libOracle.so: oracle.o
${CXX} -shared -fPIC -o libOracle.so oracle.o
libSybase.so: sybase.o
${CXX} -shared -fPIC -o libSybase.so sybase.o
libdb.so: database.o
${CXX} -shared -fPIC -o libdb.so database.o
clean:
rm -f *.o *.so main_dlopen main_direct
%.o : %.cpp
${CXX} ${CXXFLAGS} -c $< -o $@
这是一个相当标准的问题:您拥有全局数据,无法控制何时初始化。
该问题还有一个标准解决方案:通过函数调用间接初始化此数据。
不要使用全局 std::map<...> DBFactory
,而是这样做:
// database.cpp
DBFactory_t& getDBFactory() {
static DBFactory_t factory;
return factory;
}
// oracle.cpp
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Oracle Proxy Constructor\n");
getDBFactory()["ORACLE"] = Maker;
}
瞧:factory
将在您第一次需要时构建。
您还有一个尚未确定的问题:oracle.cpp
和 sybase.cpp
都定义函数 Maker
、class proxy
和变量 p
在全局命名空间中,如果两个文件都加载到单个进程中,则会导致 ODR 违规和未定义的行为。您最好使用单独的名称空间来避免这种情况。
见长见谅post。这是一个复杂的问题,我想要一个完整的描述。
在 Linux Fedora 21 (g++ 4.9.2) 等上。
我正在使用 "database" 的基础 class 并继承 Oracle 和 Sybase 的 classes 开发 C++ 数据库包装器库。
除此之外,我希望某些程序能够使用 dlopen 在 运行 时动态加载库,遵循 http://www.linuxjournal.com/article/3687?page=0,0 和其他人的模板。
其他程序直接link他们需要的版本。我的解决方案的问题是 database.cpp 中存在静态 std::map(请参阅下面的代码),必须在将其分配给 oracle / sybase.cpp 的静态初始化程序之前对其进行初始化。我能够做到这一点的唯一方法是在编译时通过 -l 参数的物理顺序(参见 Makefile)。 让它们正确:,程序 运行 没问题。 向后获取它们: -- 程序编译并 links 找到,但会在执行时立即崩溃。这让我很困扰,因为我更喜欢成功的 compile/link 来产生成功的 运行 而图书馆顺序的看似翻转通常不会这样做。我知道 link 命令导致 link 错误的情况并不少见,但不会导致 运行 时间错误。
两个问题:
- 除了库顺序之外,我可以使用其他一些 link 选项来确保正确初始化吗?
- 谁能看到另一种算法可以消除这种依赖性。 (我不希望普通程序必须声明 DBFactory,它需要留在库中,但 main_dlopen 中的额外步骤就可以了)。
这是代码、所有示例模块和 Makefile。包括 2 个主要程序,一个用于正常 linking (main_direct) 和一个用于 运行time linking (main_dlopen)。两者都将按照给定的方式编译和运行(除非有剪切粘贴错字)。
感谢您的关注...
// database.h
#include <map>
#include <string>
#include <iostream>
#include <stdio.h>
class Database; // forward declaration
typedef Database* maker_t();
// our global factory
typedef std::map<std::string, maker_t *> DBFactory_t;
extern DBFactory_t DBFactory;
class Database
{
public:
Database () {}
virtual ~Database () {};
};
// database.cpp
#include "database.h"
__attribute__((constructor)) void fooDatabase (void) {
fprintf (stderr, "Database library loaded\n");
}
// our global factory for making dynamic loading possible
// this is the problem child static global
std::map<std::string, maker_t * > DBFactory ;
// oracle.h
#include "database.h"
class Oracle : public Database
{
public:
Oracle ();
virtual ~Oracle ();
};
// oracle.cpp class.
#include "oracle.h"
using namespace std;
__attribute__((constructor)) void fooOracle (void) {
fprintf (stderr, "Oracle library loaded\n");
}
// the following code follows the referece at
// http://www.linuxjournal.com/article/3687?page=0,0
extern "C" {
Database * Maker()
{
return new Oracle;
}
class proxy {
public:
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Oracle Proxy Constructor\n");
DBFactory["ORACLE"] = Maker;
}
};
}
proxy p;
Oracle::Oracle () {
cout << "Oracle Constructor" << endl;
}
Oracle::~Oracle ()
{
cout << "Oracle Destructor" << endl;
}
// sybase.h
#include "database.h"
class Sybase : public Database
{
public:
Sybase ();
virtual ~Sybase();
};
// sybase.cpp class.
#include "sybase.h"
using namespace std;
__attribute__((constructor)) void fooSybase (void) {
fprintf (stderr, "Sybase library loaded\n");
}
extern "C" {
Database * Maker()
{
return new Sybase;
}
class proxy {
public:
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Sybase Proxy Constructor\n");
DBFactory["SYBASE"] = Maker;
}
};
}
proxy p;
Sybase::Sybase () {
cout << "Sybase Constructor" << endl;
}
Sybase::~Sybase ()
{
cout << "Sybase Destructor" << endl;
}
// main_direct.cpp
#include "oracle.h"
int main ()
{
Oracle db ();
return 0;
}
// main_dlopen.cpp
#include <iostream>
#include <dlfcn.h>
#include <stdlib.h>
#include "database.h"
using namespace std;
int main ()
{
void * dl = dlopen ("libSybase.so", RTLD_NOW);
if (!dl) { cerr << dlerror() << endl; exit (1); }
Database * db = DBFactory["SYBASE"] ();
delete db;
return 0;
}
#Makefile
CXXFLAGS = -Wall -fPIC -ggdb -std=c++11
all: main libOracle.so libSybase.so libdb.so
main: main_dlopen main_direct
main_dlopen: main_dlopen.o libdb.so libOracle.so libSybase.so
${CXX} -o main_dlopen main_dlopen.o -L. -ldb -ldl
# reverse -lOracle and -ldb to make this program crash
main_direct: main_direct.o libdb.so libOracle.so libSybase.so
${CXX} -o main_direct main_direct.o -L. -lOracle -ldb
libOracle.so: oracle.o
${CXX} -shared -fPIC -o libOracle.so oracle.o
libSybase.so: sybase.o
${CXX} -shared -fPIC -o libSybase.so sybase.o
libdb.so: database.o
${CXX} -shared -fPIC -o libdb.so database.o
clean:
rm -f *.o *.so main_dlopen main_direct
%.o : %.cpp
${CXX} ${CXXFLAGS} -c $< -o $@
这是一个相当标准的问题:您拥有全局数据,无法控制何时初始化。
该问题还有一个标准解决方案:通过函数调用间接初始化此数据。
不要使用全局 std::map<...> DBFactory
,而是这样做:
// database.cpp
DBFactory_t& getDBFactory() {
static DBFactory_t factory;
return factory;
}
// oracle.cpp
proxy ()
{
// register the maker with the factory
fprintf (stderr, "Oracle Proxy Constructor\n");
getDBFactory()["ORACLE"] = Maker;
}
瞧:factory
将在您第一次需要时构建。
您还有一个尚未确定的问题:oracle.cpp
和 sybase.cpp
都定义函数 Maker
、class proxy
和变量 p
在全局命名空间中,如果两个文件都加载到单个进程中,则会导致 ODR 违规和未定义的行为。您最好使用单独的名称空间来避免这种情况。