如果我在C ++中使用dlopen
之类的东西加载某些符号,而该翻译单元中的其他类具有static
成员变量,那么这些静态成员变量的行为究竟是什么。它们是否被初始化或没有被初始化或者没有,因为库并没有真正加载你查找的符号(我认为后者不是真的,因为如果你查找的符号需要那些需要加载的符号太)?
答案 0 :(得分:3)
简而言之,无法保证在引用同一转换单元中的外部可见函数或变量之前,将初始化在编译时无法初始化的静态变量。即使对于静态链接也是如此。至于尝试在动态加载的库中获取静态变量以在加载时初始化,我的经验是,通常你会很幸运,特别是对于小程序,但从根本上说这是未定义的行为,不应该依赖。由此产生的错误是不可预测的,难以重现,并且高度系统特定。
首先,一些标准和解释为什么这是未定义的行为,然后是一些解决方法。
遗憾的是,静态这个词在标准中超载了,所以请耐心等待。标准引用静态存储持续时间和静态初始化。标准定义的存储持续时间类型是静态,线程,自动和动态。它们就像听起来一样。静态存储持续时间意味着此类变量的生命周期是程序的整个持续时间。
静态初始化是一个独特的概念。虽然每个程序执行一次变量只能存储一次,但是程序启动时可能无法知道它初始化的值。在程序开始时,具有静态存储持续时间的所有变量将初始化为零,然后可以将其初始化为常量。精细点在§3.6.2中,但粗略地说,如果静态变量的初始化仅依赖于常量表达式,则它将被初始化。零初始化和常量初始化一起称为静态初始化。对应的是动态初始化。这些是有趣的,但不幸的是,在动态链接的情况下,或者在main()
返回之前,在动态加载的情况下,没有可移植的方法强制动态初始化在dlopen()
首次执行之前发生。 C ++根本不需要这样的。
C ++ 11标准的关键部分在§3.6.2中:
它是实现定义的动态初始化 具有静态存储持续时间的非局部变量在之前完成 主要的第一个声明。如果初始化推迟到某些 在主要的第一个陈述之后的时间点,它应该在之前发生 第一个odr-use(3.2)中定义的任何函数或变量 与要初始化的变量相同的翻译单元。
尽管如此,如果你已经进行过实验,你会注意到有时这确实有效。有时您可以通过将其填充到静态变量的构造函数中来获取在库加载时运行的任意代码。是否发生这种情况仅取决于编译器(而不是链接器)。 dlopen的联合帮助页解释了。
如果动态库导出名为_init()的例程,则在加载之后执行该代码,然后dlopen()返回
检查用标准C ++编写的小型共享对象的asm输出,我可以看到clang 3.4和g ++ 4.8都添加了一个_init部分,但不要求它们这样做。
至于变通方法,已经变得司空见事的gcc扩展确实可以控制这种行为。通过向函数添加构造函数属性,我们可以坚持在库初始化时运行它们。 dlopen的链接联机帮助页建议使用此方法。
请参阅功能属性GCC documentation和this SO question,其中包含示例用法。这个扩展由gcc,clang,IBM XL支持,我的猜测是icc也支持它。 MSVC不支持这一点,但我知道有类似的东西。
真正便携的解决方案难以捉摸。正如标准所说,如果你能以某种方式在与静态变量相同的翻译单元中引起odr使用,那么必须初始化静态变量。为此目的调用一个函数,甚至是一个虚函数都可以。