【问题标题】:Are static c++ objects in dynamically loaded libraries initialized before dlopen() returns?动态加载的库中的静态 c++ 对象是否在 dlopen() 返回之前初始化?
【发布时间】:2017-02-28 04:00:17
【问题描述】:

下面的代码演示了预期的(并且可以说是直观的)行为。正如在输入main() 之前初始化可执行文件中的静态对象一样,人们会期望在dlopen() 返回之前初始化动态加载的库中的静态对象。

问题:在运行时加载的库的这种行为是否以任何方式得到保证,还是只是一个方便的意外或幸运的实现细节?我们是否可以依赖被调用的库中静态对象的构造函数,或者我们是否必须求助于诸如标有__attribute__((constructor)) 的函数之类的替代方法,以确保在dlopen() 调用范围内实现某些期望的行为?

// libtest.cpp
#include <iostream>

namespace
{
    class Test
    {
    public:
        Test()  { std::cerr << "In Test()...\n"; }
        ~Test() { std::cerr << "In ~Test()...\n"; }
    };

    Test    test; // when is this initialized?
}

// testso.cpp
#include <dlfcn.h>
#include <iostream>

int main( int ac, char* av[] )
{
    if ( ac < 2 )
    {
        std::cerr << "Usage: " << av[0] << "library-name\n";
        return 1;
    }
    std::cerr << "Before dlopen()...\n";
    ::dlerror();
    void*    _handle(::dlopen( av[1], RTLD_NOW ));
    std::cerr << "After dlopen()...\n";
    if ( !_handle )
    {
        std::cerr << "Error: " << ::dlerror() << ", exiting...\n";
        return 2;
    }
    ::dlclose( _handle );
    std::cerr << "After dlclose()...\n";
    return 0;
}

编译并运行(注意Test() 调用之前dlopen() 返回):

$ g++ -o libtest.so -shared -fPIC libtest.cpp
$ g++ -o testso -ldl testso.cpp
$ ./testso ./libtest.so
Before dlopen()...
In Test()...
After dlopen()...
In ~Test()...
After dlclose()...
$ 

【问题讨论】:

  • 第一个提示:不要使用__attribute__((constructor))如果可以避免。它通常被认为是一种 hack 并且不可移植。请改用导出的函数。只是把那个强制性的警告标签扔在那里。
  • 必须从外部调用导出的函数;而构造函数例程是自动运行的,实际上不需要任何外部操作(除了启动库本身的负载)。当需要后一种行为时,问题是静态对象的构造函数是否可以实现这一点。然后,“构造函数”将适用于 C 等本身没有构造函数概念的语言。

标签: c++


【解决方案1】:

第一件事:dlopen()难以置信特定于平台的,与早期疾病症状和 WebMD 的情况一样,您应该始终咨询您平台的相关 man 页面。

即使dlopen() 系列函数似乎符合IEEE Standard 1003.1, 2004 Edition,这也是正确的,尽管我无法告诉您今天的系统与这些标准的兼容性如何(例如,Windows 的@987654322 有着悠久的历史@)。


在 OS/X / BSD 上,

dlopen() 检查path 指定的mach-o 文件。如果文件与当前进程兼容并且尚未加载到 当前进程,它被加载并链接。 链接后,如果它包含任何初始化函数,则在dlopen()之前调用它们 返回。

强调我的。


在 Linux 上,

共享对象可以使用 __attribute__((constructor))__attribute__((destructor)) 函数 属性。 构造函数在dlopen()之前执行 返回,析构函数在dlclose()之前执行 返回。

强调我的。


在 Solaris 上,

作为加载新对象的一部分,在dlopen() 返回之前调用对象内的初始化代码。此初始化是用户代码,因此可能会产生无法捕获的错误dlopen().

强调我的。

【讨论】:

  • 如果进程已经有多个线程怎么办 - 其他线程是否可能(意外地)在 dlopen 返回之前开始使用 dl?当然,初始化器可以做一些事情来让其他线程参与进来,但假设没有做这么明确的事情。
  • @joeking 这就是好的并发实践的意义所在 :) 我不会假设系统调用的并发性,尤其是脆弱的dlopen() 家族(轶事但一般好建议)。
  • 是的 - 这是 Windows 和 Linux 具有相似功能但在细节上存在 巨大 差异的领域。在 Windows 上,“DllMain()”是调用初始化程序的东西,它有点单线程,因为操作系统持有 loaderlock(尽管具体的效果没有明确记录)。结果是您必须小心从这些初始化程序中调用了哪些操作系统函数
  • @joeking 是的,Windows 是 Initializers are a hack comment 评论的缩影。我通常在将它们注入其他进程时使用它们,尽管还有更有效的方法来初始化 DLL(例如导出函数代理等)
  • documentation 声明 .ctors 部分具有指向标记函数以及 C++ 构造函数的指针,所以我认为这解决了问题:-)
猜你喜欢
  • 2018-01-17
  • 2020-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多