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

Posted

技术标签:

【中文标题】动态加载的类的静态成员变量【英文标题】:Static member variable for class that is dynamically loaded 【发布时间】:2014-07-09 14:13:31 【问题描述】:

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

【问题讨论】:

When are static C++ class members initialized?的可能重复 不重复这与在运行时加载库以及静态成员的作用有关 很公平,已撤回投票 - 也许您应该使用您的操作系统进行标记,因为我觉得这将取决于实现,而不是标准中指定的内容。 【参考方案1】:

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

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

不幸的是,静态这个词在标准中被重载了,所以请耐心等待。该标准同时提及静态存储持续时间静态初始化。该标准定义的存储持续时间类型有静态、线程、自动和动态。它们就像听起来一样。静态存储时长表示这样一个变量的生命周期就是整个程序的时长。

静态初始化是一个独特的概念。尽管每次程序执行一个变量可能只存储一次,但在程序启动时可能不知道初始化它的值。在程序开始时,所有具有静态存储持续时间的变量都将被初始化为零,而那些可以被初始化的变量将被常量初始化。要点在 §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,则必须初始化静态变量。调用一个函数,甚至只是为此目的调用一个虚拟函数,都可以。

【讨论】:

以上是关于动态加载的类的静态成员变量的主要内容,如果未能解决你的问题,请参考以下文章

随笔25 java中的类加载顺序

Java中的static关键字

Java中的类的field到底是指啥?

类的加载过程和对象的创建

静态变量和成员变量的区别

静态变量和成员变量的区别