动态加载的类的静态成员变量

问题描述 投票:2回答:1

如果我在C ++中使用类似dlopen的东西加载某些符号,而该翻译单元中的其他类具有static成员变量,那么这些静态成员变量的行为究竟是什么。它们是初始化还是没有,因为库并没有真正加载你查找的符号(我认为后者不正确,因为如果你查找的符号需要那些它们也需要加载)?

c++ linux shared-libraries dynamic-loading
1个回答
3
投票

简而言之,无法保证在引用同一转换单元中的外部可见函数或变量之前,将初始化在编译时无法初始化的静态变量。即使对于静态链接也是如此。至于尝试在动态加载的库中获取静态变量以在加载时初始化,我的经验是,通常你会很幸运,特别是对于小程序,但从根本上说这是未定义的行为,不应该依赖。由此产生的错误是不可预测的,难以重现,并且高度系统特定。

首先,一些标准和解释为什么这是未定义的行为,然后是一些解决方法。

不幸的是,静态这个词在标准中超载了,所以请耐心等待。标准引用了静态存储持续时间和静态初始化。标准定义的存储持续时间类型是静态,线程,自动和动态。它们就像听起来一样。静态存储持续时间意味着这种变量的生命周期是程序的整个持续时间。

静态初始化是一个独特的概念。虽然每个程序执行一次变量只能存储一次,但是程序启动时可能无法知道它初始化的值。在程序开始时,具有静态存储持续时间的所有变量将初始化为零,然后可以将其初始化为常量。精细点在§3.6.2中,但粗略地说,如果静态变量的初始化仅依赖于常量表达式,则它将被初始化。零初始化和常量初始化一起称为静态初始化。对应的是动态初始化。这些是有趣的,但遗憾的是,在动态链接的情况下,或者在动态加载的情况下,在main()返回之前,没有可移植的方法强制动态初始化在dlopen()首次执行之前发生。 C ++根本不要求这样。

C ++ 11标准的关键部分在§3.6.2中:

实现定义是否在第一个main语句之前完成具有静态存储持续时间的非局部变量的动态初始化。如果初始化延迟到第一个main语句之后的某个时间点,它应该在与要初始化的变量相同的转换单元中定义的任何函数或变量的第一个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使用,那么必须初始化静态变量。调用函数,甚至是为此目的的虚函数,都可以。

© www.soinside.com 2019 - 2024. All rights reserved.