【问题标题】:How is static variable initialization implemented by the compiler?编译器如何实现静态变量初始化?
【发布时间】:2010-10-28 06:13:31
【问题描述】:

我很好奇函数中静态变量的底层实现。

如果我声明一个基本类型(char、int、double 等)的静态变量,并给它一个初始值,我想编译器只是在程序一开始就设置了该变量的值在调用main() 之前:

void SomeFunction();

int main(int argCount, char ** argList)
{
    // at this point, the memory reserved for 'answer'
    // already contains the value of 42
    SomeFunction();
}

void SomeFunction()
{
    static int answer = 42;
}

但是,如果静态变量是类的实例:

class MyClass
{
    //...
};

void SomeFunction();

int main(int argCount, char ** argList)
{
    SomeFunction();
}

void SomeFunction()
{
    static MyClass myVar;
}

我知道在第一次调用该函数之前它不会被初始化。由于编译器无法知道函数何时第一次被调用,它是如何产生这种行为的?它本质上是在函数体中引入一个 if 块吗?

static bool initialized = 0;
if (!initialized)
{
    // construct myVar
    initialized = 1;
}

【问题讨论】:

    标签: c++ compiler-construction static initialization


    【解决方案1】:

    This question 涵盖了类似的内容,但没有提及线程安全。无论如何,C++0x 将使函数静态初始化线程安全。

    (参见C++0x FCD, 6.7/4 on function statics:“如果控制在变量初始化时同时进入声明,则并发执行应等待 初始化完成。”)

    另一件没有提到的事情是函数静态在其构造的相反顺序被破坏,因此编译器维护一个析构函数列表以在关闭时调用(这可能与 atexit 使用的列表相同,也可能不同)。

    【讨论】:

    • 你能提供一个参考/引用它在 C++0x 中是线程安全的吗?我还没找到。
    【解决方案2】:

    另一个转折出现在嵌入式代码中,其中 run-before-main() 代码(cinit/whatever)可以将预初始化数据(静态和非静态)从 const 数据段复制到 ram 中,可能位于只读存储器。这在代码可能不是从某种可以重新加载的后备存储(磁盘)运行的情况下很有用。同样,这并不违反语言的要求,因为这是在 main() 之前完成的。

    轻微切线:虽然我没有看到它做了太多(在 Emacs 之外),但程序或编译器基本上可以在进程中运行您的代码并实例化/初始化对象,然后冻结并转储进程。 Emacs 做了类似的事情来加载大量的 elisp(即咀嚼它),然后将运行状态转储为工作可执行文件,以避免每次调用的解析成本。

    【讨论】:

      【解决方案3】:

      在我看到的编译器输出中,函数局部静态变量的初始化与您想象的完全一样。

      请注意,通常这不是以线程安全的方式完成的。因此,如果您有可能从多个线程调用的带有静态局部变量的函数,您应该考虑到这一点。在调用任何其他函数之前在主线程中调用一次函数通常可以解决问题。

      我应该补充一点,如果本地静态的初始化是通过像您的示例中那样的简单常量,编译器不需要经过这些回转 - 它可以初始化图像中的变量或main()之前的变量就像一个常规的静态初始化(因为你的程序无法区分)。但是如果你用函数的返回值初始化它,那么编译器几乎必须测试一个标志,表明初始化是否已经完成或类似的东西。

      【讨论】:

      • 有什么变化吗?我听说在 c++11 之后,静态的每个初始化都是线程安全的。
      • @VictorPolevoy:是的——写这个答案时,C++11 不存在。在 C++11 中,标准包括线程支持,这被添加到块范围静态变量的初始化描述中(6.7/4):“如果控制在变量初始化时同时进入声明,则并发执行应等待初始化完成”。
      • 我建议您编辑您的答案,因为它被接受为答案并且有 10 票。此外,这个问题在静态变量的初始化主题中最为明显。
      • 感谢关于通过简单常量与函数返回值进行初始化的部分!好点子!
      【解决方案4】:

      我知道在第一次调用该函数之前它不会被初始化。由于编译器无法知道函数何时第一次被调用,它是如何产生这种行为的?它本质上是在函数体中引入一个 if 块吗?

      是的,没错:而且,FWIW,它不一定是线程安全的(如果函数被两个线程同时“第一次”调用)。

      出于这个原因,您可能更喜欢在全局范围内定义变量(尽管可能在类或命名空间中,或者在没有外部链接的情况下静态)而不是在函数内,以便在程序启动之前初始化它而无需任何运行时“如果”。

      【讨论】:

        【解决方案5】:

        您对所有事情都是正确的,包括作为常见实现的初始化标志。这就是为什么静态局部变量的初始化不是线程安全的,也是 pthread_once 存在的原因。

        一个小小的警告:编译器必须发出“表现得好像”静态局部变量在第一次使用时构造的代码。由于整数初始化没有副作用(并且不调用用户代码),因此在初始化 int 时由编译器决定。用户代码不能“合法地”找出它的作用。

        显然,您可以查看汇编代码,或引发未定义的行为并从实际发生的情况中进行推断。但是 C++ 标准并没有将其视为声称该行为不是“好像”它按照规范所说的那样的有效理由。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-08-22
          • 2010-12-22
          • 2010-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多