函数级静态变量何时分配/初始化?


89

我非常有信心在程序启动时分配(并初始化,如果适用)全局声明的变量。

int globalgarbage;
unsigned int anumber = 42;

但是函数中定义的静态变量呢?

void doSomething()
{
  static bool globalish = true;
  // ...
}

什么时候globalish分配空间?我猜该程序何时启动。但是它也会被初始化吗?还是在doSomething()第一次调用时初始化?

Answers:


91

我对此感到很好奇,因此我编写了以下测试程序,并使用g ++ 4.1.2版对其进行了编译。

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

结果不是我预期的。直到第一次调用该函数时,才调用静态对象的构造函数。这是输出:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

29
澄清一下:静态变量是在执行第一次达到其声明时进行初始化的,而不是在调用包含函数时进行的初始化。如果在函数的开始处只有一个静态变量(例如,在您的示例中),则它们是相同的,但不一定如此:例如,如果您具有'if(...){static MyClass x; ...}”,则在if语句的条件评估为false的情况下,在该函数的首次执行过程中不会将x初始化为ALL。
伊万

4
但这是否会导致运行时开销,因为每次使用静态变量时,程序都必须检查它是否以前已使用过,如果没有使用过,则必须对其进行初始化?在那种情况下,这种感觉有点糟。
HelloGoodbye

完美的例证
Des1gnWizard

@veio:是的,初始化是线程安全的。看到问题的更多详细信息:stackoverflow.com/questions/23829389/...
雷米

2
@HelloGoodbye:是的,这会导致运行时开销。也看到了这个问题:stackoverflow.com/questions/23829389/...
雷米

53

C ++标准的一些相关词汇:

3.6.2初始化非本地对象[basic.start.init]

1个

具有静态存储持续时间(basic.stc.static)的对象的存储应在进行任何其他初始化之前进行零初始化(dcl.init)。具有静态存储持续时间的POD类型(basic.types)的对象(使用常量表达式(expr.const)初始化)应在进行任何动态初始化之前进行初始化。在同一翻译单元中定义并动态初始化且具有静态存储持续时间的名称空间范围的对象,应按照其定义在翻译单元中出现的顺序进行初始化。[注意: dcl.init.aggr 描述聚合成员的初始化顺序。本地静态对象的初始化在stmt.dcl中描述。]

[下面的更多文字为编译器作者增加了更多自由]

6.7声明声明[stmt.dcl]

...

4

在进行任何其他初始化之前,将对所有具有静态存储持续时间(basic.stc.static)的本地对象执行零初始化(dcl.init)。具有POD类型(basic.types)且静态存储持续时间已通过常量表达式初始化的本地对象,在首次输入其块之前会被初始化。在相同的条件下,允许实现对具有静态存储持续时间的其他本地对象执行早期初始化,而在相同的条件下,允许实现对名称空间范围内的具有静态存储持续时间的对象进行静态初始化(basic.start.init)。否则,将在控件第一次通过其声明时初始化此类对象。这样的对象在其初始化完成时被视为已初始化。如果初始化由于抛出异常而退出,则初始化未完成,因此下次控件进入声明时将再次尝试。如果控件在初始化对象时(递归)重新输入声明,则该行为未定义。[ 示例:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- 结束示例 ]

5

当且仅当构造变量时,才会执行具有静态存储持续时间的本地对象的析构函数。[注意: basic.start.term 描述具有静态存储持续时间的本地对象的销毁顺序。]


这回答了我的问题,与公认的答案不同,它不依赖“轶事证据”。我正在特别寻找在静态初始化函数本地静态对象的构造函数中提到的异常:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge

26

所有静态变量的内存在程序加载时分配。但是局部静态变量是在第一次使用它们时创建的,并在程序启动时进行了初始化。还有关于一些良好的阅读,和静在一般情况下,在这里。总的来说,我认为其中一些问题取决于实现方式,尤其是如果您想知道这些东西在内存中的位置。


2
不完全是,会分配局部静态变量,并在“程序加载时”对它们进行零初始化(用引号引起,因为这也不完全正确),然后在首次输入其所使用的函数时进行重新初始化。
Mooing Duck 2012年

看起来好像7年后,该链接已断开。
史蒂夫

1
是的,链接断开了。这是一个存档:web.archive.org/web/20100328062506/http
尤金(Eugene)

10

编译器将foo在程序加载时分配在函数中定义的静态变量,但是编译器还将向您的函数中添加一些其他指令(机器代码),foo以便在首次调用此附加代码时将初始化静态变量(例如,调用构造函数(如果适用)。

@Adam:编译器在后台注入代码的原因是您看到结果的原因。


5

我尝试再次测试来自Adam Pierce的代码,并添加了另外两种情况:类中的静态变量和POD类型。我的编译器是Windows OS(MinGW-32)中的g ++ 4.8.1。结果是类中的静态变量与全局变量相同。在进入主函数之前将调用其构造函数。

  • 结论(对于g ++,Windows环境):

    1. 全局变量类中的静态成员:在进入函数(1)之前调用构造函数。
    2. 局部静态变量:仅在执行第一次达到其声明时才调用构造函数。
    3. 如果Local静态变量是POD类型,则在进入功能(1)之前也将其初始化。POD类型的示例:static int number = 10;

(1):正确的状态应为:“在调用来自同一翻译单元的任何函数之前”。但是,为简单起见,如下面的示例所示,它是主要功能。

包括<iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

结果:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

有人在Linux环境中测试过吗?


3

静态变量分配在代码段内-它们是可执行映像的一部分,因此被映射到已初始化的映像中。

函数范围内的静态变量被视为相同,作用域纯粹是语言级别的构造。

因此,可以保证将静态变量初始化为0(除非您指定其他内容)而不是未定义的值。

您可以利用初始化的其他方面-例如共享段允许一次运行的可执行文件的不同实例访问相同的静态变量。

在C ++(全局范围内)中,静态对象在C运行时库的控制下将其构造函数作为程序启动的一部分进行调用。在Visual C ++下,至少可以通过init_seg编译指示来控制对象的初始化顺序。


4
这个问题是关于函数范围的静态的。至少当它们具有非平凡的构造函数时,它们会在首次进入函数时进行初始化。更具体地说,当到达那条线时。
亚当·米兹

是的-问题是关于分配给变量的空间,并使用简单的数据类型。该空间仍在代码段中分配
Rob Walker

我在这里看不到代码段与数据段的真正关系。我认为我们需要OP进行澄清。他确实说过“并且已初始化(如果适用)”。
亚当·米兹

5
变量永远不会在代码段内分配;这样,它们将无法写入。
botismarius

1
静态变量是在数据段中还是在bss段中分配的空间,具体取决于它们是否已初始化。
EmptyData

3

还是在首次调用doSomething()时将其初始化?

是的。除此之外,它还使您可以在合适的时候初始化全局访问的数据结构,例如在try / catch块内部。例如代替

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

你可以写

int& foo() {
  static int myfoo = init();
  return myfoo;
}

并在try / catch块中使用它。在第一次调用时,变量将被初始化。然后,在第一次和下一次调用时,将返回其值(通过引用)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.