【问题标题】:Linux C++ Dynamic Libs and static initialization orderLinux C++ 动态库和静态初始化顺序
【发布时间】:2015-05-31 21:35:49
【问题描述】:

请原谅我的长篇大论。这是一个复杂的问题,我想要一个完整的描述。

在 Linux Fedora 21 (g++ 4.9.2) 等上。

我正在使用“数据库”基类以及 Oracle 和 Sybase 的继承类来开发 C++ 数据库包装库。

除此之外,我希望一些程序能够在运行时使用 dlopen 动态加载库,遵循 http://www.linuxjournal.com/article/3687?page=0,0 和其他人的模板。

但是其他程序直接链接他们需要的版本。我的解决方案的问题是 database.cpp 中存在一个静态 std::map (请参见下面的代码),必须先对其进行初始化,然后才能在 oracle / sybase.cpp 的静态初始化程序中对其进行分配。我能够做到这一点的唯一方法是在编译时通过 -l 参数的物理顺序(参见 Makefile)。 让他们正确:,程序运行良好。 让它们倒退: -- 程序编译和链接找到,但在执行时会立即崩溃。这让我很困扰,因为更喜欢成功的编译/链接以产生成功的运行,而看似翻转的库顺序通常不会这样做。我知道链接顺序会导致链接错误并不少见,但不会导致运行时错误。

两个问题:

  1. 除了库顺序之外,我还可以使用其他链接选项来确保正确初始化吗?
  2. 任何人都可以看到另一种可以消除这种依赖性的算法。 (我不希望普通程序必须声明 DBFactory,它需要保留在库中,但在 main_dlopen 中多做一步就可以了)。

这里是代码、所有示例模块和 Makefile。包括2个主程序,一个用于正常链接(main_direct),一个用于运行时链接(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 $@

【问题讨论】:

    标签: c++ linux dynamic initialization shared-libraries


    【解决方案1】:

    这是一个相当标准的问题:你有全局数据,无法控制它何时初始化。

    该问题还有一个标准解决方案:通过函数调用间接初始化此数据。

    不要使用全局std::map&lt;...&gt; 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.cppsybase.cpp 在全局命名空间中定义函数 Makerclass proxy 和变量 p,导致违反 ODR 和未定义行为如果两个文件都加载到一个进程中。您最好使用单独的命名空间来避免这种情况。

    【讨论】:

    • 做到了。 Acutally 比我想象的更简单的解决方案。非常感谢。
    • 你是对的,这是处理静态初始化顺序问题的最简单的解决方案。
    • 尽管这是一个更简单的解决方案,但这可能不适用于多个线程(两个线程同时调用工厂)。在初始化之前您需要一个互斥锁,如果您多次调用工厂,这可能会花费您。
    • @Hans 初始化函数静态对象由(我相信)C++11 保证线程安全,不需要锁。 g++ 提供了早于 2011 年的保证,但如果您的编译器不符合 C++11 并且您正在执行多线程编程,那么您确实需要一个锁。
    • @EmployedRussian 是的,你是对的。 C++11 及更高版本保证这是线程安全的
    猜你喜欢
    • 2021-07-12
    • 1970-01-01
    • 2015-06-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    相关资源
    最近更新 更多