'cppcoreguidelines-interfaces-global-init' 仅在特定情况下出错

The 'cppcoreguidelines-interfaces-global-init' goes wrong just in a specific scenario

鉴于 cppcoreguidelines-interfaces-global-init,特别是 “根据未初始化的非局部变量使用非常量表达式初始化非局部变量” ,即here,我有以下场景:

然后,我发现了如下所示的本地 static,上面的警告以错误的初始化结束。

static int GlobalScopeBadInit1 = ExternGlobal;

到目前为止一切顺利 - 这是一个糟糕的初始化,可能会出错,我们需要修复它。

问题是:为什么我的机器就出错了?无论我们多么努力地尝试 - 调试或发布,它都会在我的机器中发生。我们已经清理并删除了其他开发人员机器上的文件,上面的代码在我的机器上有 100% 的错误率,而在另一个开发人员的机器上有 0% 的错误率。 它也不会发生在构建机器上。

有人知道可以解释这种行为的原因吗?

谢谢。

当一个翻译单元中的对象可能依赖来自另一个单元的尚未初始化或正在初始化的数据时,就会出现您所遭受的 static initialization order fiasco

Dynamic initializationstatic 个翻译单元的成员 不确定排序 相对于所有其他翻译单元。无法保证它们的初始化顺序,即使它们在同一线程上也是如此。

我能想到的几种可能情况可能会导致您遇到这种情况,而其他人则不会。硬件、link-顺序、时间等因素都可能起作用。一些可能的情况:

  1. 由于动态初始化是不确定的顺序并且不能保证甚至存在于同一个线程中,如果初始化在启动时是多线程的,那么不同的硬件可能会引入不同的初始化时间(竞争条件)

  2. 可能只是你的系统开发环境不同而已。像在不同位置找到库这样小的事情可能会扰乱库加载顺序,这可能会影响事物的初始化顺序。

  3. 假设您的系统正在执行并行编译,您的硬件可能正在以不同的顺序编译对象,这影响了这些对象获得 linked 的顺序。link-order 可能会更改初始化顺序 -- 这可能会导致您遇到其他人不会遇到的问题。

最终,为什么你会遇到这种情况而其他人却没有,这实际上并不重要。正式地,您正在经历 未定义的行为,并且它的行为方式和它歧视的人无法解释


注意:如果没有更多关于您的系统设置的信息,可以提供的最好的就是猜测为什么会发生这种情况。

您遇到的问题是由 static initialization order fiasco 引起的,简而言之,不同编译单元中静态和全局变量的构造和销毁顺序未指定。

事实上,在您的情况下,问题仅在特定环境中发生(即初始化顺序导致问题),而不是在另一个环境中,这正是 未指定顺序 的含义:它受编译顺序的影响,并且可能受编译器优化和其他可能与环境相关的考虑因素的影响。它甚至可以在不同的线程中同时初始化(参见:C++ spec [stmt.dcl] and a reason for why and when that may happen at the proper section in the original working doc dealing with that issue)。

有静态初始化顺序失败的解决方案吗?

是的,有几种可能的解决方案。


第一个解决方案可能是重新设计

重新设计选项 1 - 更改代码,这样您就不会拥有多个全局对象。

您可以处理单个实际全局对象中的所有其他“全局变量”。有一些库以这种方式处理单例,在一个全局 SingletonManager 中管理所有单例。但由于这样的改动可能需要进行相当多的代码改动,伴随着风险,您可能需要考虑其他选择。

重新设计选项 2 - 使用静态或全局函数而不是全局变量 - 一旦从如下所示的静态或全局函数中检索到全局变量,初始化顺序就解决了:

Boo& get_global_boo() {
    static Boo b(get_global_foo());
    return b;
}

Foo& get_global_foo() {
    static Foo f(42);
    return f;
}

你可以比较this code example facing the initialization order fiasco to one which solves the issue with static methods。这种方法有时被称为“Meyers Singleton”,代表 Scott Meyers,他在他的书 “More Effective C++”.

中讨论了这种方法

可以找到有关该方法的更多信息 here and here

重新设计选项 3 - 一种更简单的重新设计方法是将所有全局变量移动到单个编译单元。这种方法需要对您的代码进行较少的更改,并且可能风险较小。但并不总是可能的。


另一种解决方案是使用编译器特定选项来设置初始化顺序 - 手动管理静态和全局初始化的顺序可以在 Visual Studio 中使用 init_seg - Visual Studio 特定 pragma 允许开发人员控制初始化顺序。参见:MSDN documentation and this blog post.

GCC 也有自己的属性用于此目的 - init_priority.


最后一个选项是最好但最复杂的 - 您可以按照 C++ 库为 std::cout 执行技巧的方式,它是一个全局对象,yet 保证在您需要时进行初始化,即使在全局上下文中也是如此。这是通过 漂亮的计数器惯用语 完成的。您可以在 in the C++ Idioms wiki and also at this SO question. Note that the nifty counter idiom is not a solution for all cases of std::cout usage in global context, and in quite rare cases there is a need to "help" it work correctly with a statement like: static std::ios_base::Init force_init; see 上阅读更多关于这个习语的内容。


有关此问题的其他讨论,另请参阅:Static variables initialisation order