【问题标题】:How does program know if static variable needs to be initialized? [duplicate]程序如何知道静态变量是否需要初始化? [复制]
【发布时间】:2018-09-08 18:47:22
【问题描述】:

如标题 - 程序如何知道 foo 在第二次调用函数时已经初始化:

int getFoo()
{
    static int foo = 30;
    return foo;
}
int main()
{
    getFoo();
    getFoo();
}

我想知道,程序是否存储了一些关于哪个静态变量已经初始化的附加信息。

编辑:
我在这里找到了答案:
Why does initialization of local static objects use hidden guard flags?
就像我猜的一样——大多数编译器都存储了额外的“保护变量”。

【问题讨论】:

标签: c++ initialization thread-safety storage-duration


【解决方案1】:

看看[stmt.dcl]/4:

  1. 具有静态存储持续时间或线程存储持续时间的块范围变量的动态初始化在控制第一次通过其声明时执行;这样的变量在其初始化完成时被认为已初始化。如果初始化抛出异常退出,说明初始化未完成,下次控件进入声明时会再次尝试。如果在初始化变量时控件同时进入声明,则并发执行应等待初始化完成。94 如果在变量初始化时控件递归地重新进入声明,则行为未定义。

【讨论】:

    【解决方案2】:

    你必须在这里小心。原语statics 在编译时初始化(只要初始化值是编译时常量,正如彼得指出的那样),因此在您的示例中,GetFoo 实际上只是返回一个常量。

    但是...

    statics 初始化对象(或通过调用函数初始化原语)在第一次进入声明它们的范围时执行所述初始化。

    此外,从 C++ 11 开始,这必须以线程安全的方式完成,这会产生大量额外的代码(尽管在第一次完成后不会产生太多的运行时开销),这可能是一个问题,例如代码大小通常很重要的微控制器。

    这是一个具体的例子:

    #include <iostream>
    
    struct X
    {
        X () { std::cout << "Initialising m\n"; m = 7; }
        int m;
    };
    
    void init_x ()
    {
        static X x;
    }
    
    int main () {
        std::cout << "main called\n";
        init_x ();
        std::cout << "init_x returned\n";
    }
    

    输出:

    main called
    Initialising m
    init_x returned
    

    现场演示:https://wandbox.org/permlink/NZApcYYGwK36vRD4

    生成代码:https://godbolt.org/z/UUcL9s

    【讨论】:

    • 原始静态变量在编译时初始化 - 如果初始化器是编译时常量!你可以像godbolt.org/z/6caWKcne8 那样做static int x = func_arg;,甚至可以调用rand() 的一些风格。也许是 PRNG 使用静态初始化器为其状态播种。但是是的,在这种情况下,初始化程序是一个常量表达式,因此不需要保护变量,它只适用于文件范围 static,除了名称的范围,它纯粹是 C++ 的东西,而不是 asm。
    • 使其线程安全是非常量初始化器的保护变量的全部意义。现实世界的编译器在 C++11 需要它之前就做到了,例如2006 年的 GCC4.1 是 Godbolt 上较早的编译器。
    • @PeterCordes 如果初始化程序是编译时常量!绝对,我已经编辑了我的帖子。 使其线程安全是非常量初始值设定项的保护变量的全部意义。 当然,但最好有办法对编译器说“不要费心如果您确定初始化只能在单线程代码路径上发生,请执行此操作。在实践中,这往往是正确的。也许我们可以有[[no_threadsafe_initializer]] static ... 或类似的。就像 C++ 中的很多东西一样,你只需要确保你做对了。
    • @PeterCordes 当然,这都是关于代码膨胀的问题,而且只有在资源匮乏的环境中,开发人员似乎关心每一个细节,这才真正重要。大多数人不需要担心这一点,宁愿安全行事。 编辑:哦,刚刚看到-fno-threadsafe-statics。也许这就够了。
    • 是的;无论哪种方式,快速路径仍然具有非常量初始值设定项的比较和分支。但是线程安全需要获取负载,这在一些弱排序的 ISA 上也可能需要内存屏障(如 ARM32,但不是 x86 或 ARM64 - A64 具有廉价的 LDAR 获取加载指令)。除此之外,这只是快速路径上的代码大小问题而不是。通过良好的布局,仅第一次调用的代码(对于 guard==0)位于函数的末尾,不会损害 I-cache 局部性或将采用的分支放在代码获取的方式中(对于超标量 CPU)。
    猜你喜欢
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    • 2016-03-05
    • 2018-06-01
    • 2010-09-17
    相关资源
    最近更新 更多