静态变量为什么只初始化一次?

58

我注意到如果你在C++中的代码中初始化静态变量,该初始化仅在第一次运行函数时运行。

这很酷,但它是如何实现的呢?它会被转换为某种扭曲的if语句吗?(如果给定一个值,那么...)

void go( int x )
{
    static int j = x ;
    cout << ++j << endl ; // see 6, 7, 8
} 

int main()
{
    go( 5 ) ;
    go( 5 ) ;
    go( 5 ) ; 
}

6
使用哪个编译器实现它? - Lightness Races in Orbit
4个回答

62

是的,通常会转换成带有内部布尔标志的隐式if语句。因此,在最基本的实现中,您的声明通常会转换为以下内容:

void go( int x ) {
  static int j;
  static bool j_initialized;

  if (!j_initialized) {
    j = x;
    j_initialized = true;
  }

  ...
} 
此外,如果您的静态对象具有非平凡析构函数,则语言必须遵守另一条规则:这些静态对象的销毁顺序必须与它们的构造顺序相反。由于构造顺序仅在运行时已知,因此销毁顺序也在运行时确定。因此,每次您使用具有非平凡析构函数的本地静态对象进行构造时,程序都必须将其注册在某种线性容器中,以便稍后以适当的顺序销毁这些对象。

不用说,实际细节取决于实现方式。


值得注意的是,当涉及到静态“原始”类型的对象(如您示例中的int)以编译时常量进行初始化时,编译器可以自由地在启动时初始化该对象。你永远不会注意到任何区别。但是,如果您使用“非原始”对象的更复杂的示例,情况就不同了。

void go( int x ) {
  static std::string s = "Hello World!";
  ...

即使对象使用编译时常量进行初始化,上述带有if的方法也应该出现在生成的代码中。

在您的情况下,初始化程序在编译时是未知的,这意味着编译器必须延迟初始化并使用隐式的if


2
我曾认为静态变量是真正的全局变量,这就是它们在线程之间共享的原因...并且作为全局变量,它们只被初始化一次... - dicroce
3
@dicroce,函数内的静态变量与真正的全局变量不同:它们是在封闭函数第一次调用时才被懒惰地初始化的。因此,您需要在函数内部进行某种形式的检查。 - Péter Török
1
@dicroce:本地静态变量最多只会被初始化一次。但是在声明时无法预先知道它们何时被初始化。如果控制流程从未经过声明,它们可能根本不会被初始化。 - AnT stands with Russia
1
@dicroce:您可能会对这个问题中关于本地静态变量和线程安全性(或某些实现中缺乏线程安全性)的讨论感兴趣:https://dev59.com/DHM_5IYBdhLWcg3wslVW - ZoogieZork
1
我认为编译器可以在编译时避免if,当j_initialized不是已知的时候。如果生成了两个版本的函数go,一个没有“if”,则一旦变量被初始化,对go的引用就可以替换。我是正确的吗? 对于大多数情况来说可能不值得这样做。随着引入更多静态变量,生成的函数数量会迅速增加。 - Joelmob
显示剩余9条评论

8

3
虽然它确实是“某种扭曲的if”,但这种扭曲可能比您想象的更多...... ZoogieZork对AndreyT的回答进行了评论,涉及到一个重要方面:静态局部变量的初始化 - 在一些编译器(包括GCC)上,默认情况下是线程安全的(编译器命令行选项可以禁用它)。因此,它使用了一些线程间同步机制(互斥锁或某种原子操作),这可能相对较慢。如果您不希望在函数中明确使用这样的操作(从性能角度考虑),则应考虑是否有低影响替代方法来延迟变量的初始化(即在某个地方以线程安全的方式显式构造它)。很少有功能如此敏感,以至于这很重要-除非您的程序太慢并且分析器正在检查该区域,否则不要让它破坏您的一天或使您的代码更加复杂。

“在某些编译器上”是错误的:静态初始化必须是线程安全的。请参见:https://dev59.com/gGsz5IYBdhLWcg3wHUS0 - underscore_d
4
@underscore_d:这个答案是针对C++03编写的,如果你想说从C++11开始发生了变化,请随意。 - Tony Delroy

1

它们仅被初始化一次,因为这是C++标准规定的。具体实现方式完全取决于编译器供应商。根据我的经验,编译器会生成并使用本地隐藏标志。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接