Hyffer
发布于 2023-05-02 / 26 阅读 / 0 评论 / 0 点赞

C++静态变量初始化顺序

问题由来

前段时间,在编写一个 C++ 项目的过程中,偶然遇到了很奇怪的bug。当时由于时间紧迫,换了种实现方式绕过了问题,没有深究。今天突然想起这件事,回滚到当时出现问题的代码,仔细调试一番,终寻其源。

为突出问题本身,下文将以这一段极为简单的程序为例,说明 C++ 静态变量的初始化顺序。

Cpp-static-init-order-sample.png

~/filetmp/test$ g++ -c C.cpp -o C.o
~/filetmp/test$ g++ main.cpp C.o -o main
~/filetmp/test$ ./main
main: 1

通过如上命令编译程序并运行,发现输出的结果是1,而不是10。

真的是bug吗

如果将编译命令稍作修改,会更让人摸不着头脑:

~/filetmp/test$ g++ -c C.cpp -o C.o
~/filetmp/test$ g++ C.o main.cpp -o main         <-- 注意这里修改了输入文件的顺序
~/filetmp/test$ ./main
main: 10

静态存储的初始化顺序

借助单步执行,我们可以很快发现问题出在int C::i = init();i = 10; 两条语句的执行顺序上。两次编译链接生成的程序中,这两条语句的执行顺序不同,也就产生了不同的输出。

造成上述结果的,实际上是 C++ 标准没有规定不同编译单元中静态变量的动态初始化顺序。在这个例子中,main.cpp 和 C.cpp 就是两个编译单元,int C::i = init();C c; 则是这两个编译单元中静态变量的初始化语句。C++ 标准对这两条语句的执行顺序不做任何保证,不同的链接顺序导致了初始化语句的不同执行顺序,从而产生了如此令人困惑的feature。或者,在查明原因之前,它应被称为bug。

代码规范

这个例子凸显了代码规范的重要性。当我们对一个复杂的系统没有透彻的了解时(通常情况也确实如此),遵循规范能很大程度上帮助我们避开令人同疼的问题。

还是以这个简单的程序为例,即使我们找到了根源,并能够得到预期的输出,但程序运行结果依赖于编译链接过程,一般情况下也是不能接受的。其实,在代码中有许多不规范的地方,修改任何一处都可以有效地规避变量初始化顺序造成的问题,这里列出两处可以优化的地方:

  • 程序中的类对象c是全局变量,将其改为main函数中的局部变量就可以解决问题,因为静态变量的初始化一定在main函数之前完成+

  • 避免使用动态初始化,改为int C::i = 1; 静态初始化。使用常量静态初始化的静态变量会由编译器写入程序数据段,在编译时完成初始化,从而不存在运行顺序的问题。(可以尝试在int C::i = 1;处打上断点,进入调试模式,你会发现这并不是一条可执行的语句。)

+此处做了简化,更严格地说:It is implementation-defined whether or not the dynamic initialization (dcl.init, class.static, class.ctor, class.expl.init) of an object of namespace scope is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized.[1]

说明

示例代码的编译运行环境为 gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04), Target: x86_64-linux-gnu,文中所述的这种“未定义行为”的运行结果十分依赖编译器的实现,因此使用不同的编译器或目标机器可能无法复现上述效果。当然,一个良好的工程,其语义应当由代码本身决定,而不应依赖于编译器和目标平台,本文提供了一个典型的负面案例。

参考

[1]When are static C++ class members initialized? - Stack Overflow

[2]Constructors, C++ FAQ: What’s the “static initialization order ‘fiasco’ (problem)”?