函数级静态变量是在何时进行分配/初始化的?

108

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

int globalgarbage;
unsigned int anumber = 42;

那么在函数内部定义的静态变量呢?

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

globalish的空间是什么时候分配的?我猜想应该是在程序开始时。但它是否也会在那时被初始化呢?还是只有在第一次调用doSomething()时被初始化?

8个回答

108

我对此很好奇,于是写了下面的测试程序,并使用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

42
澄清一下:静态变量在首次执行声明时初始化,而不是调用其所在函数时。如果你只是在函数开头有一个静态变量(例如你的例子),那么这两者是相同的,但不一定总是相同的:例如,如果你有“if (...) { static MyClass x; ... }”,则当 if 语句的条件计算结果为 false 时,在该函数的第一次执行期间根本不会初始化 'x' 变量。 - EvanED
7
但这难道不会导致一些运行时开销吗?因为每次使用静态变量时,程序都必须检查它是否被先前使用过,如果没有,则必须进行初始化。如果是这种情况的话,有点糟糕。 - HelloGoodbye
1
@veio:是的,初始化是线程安全的。有关更多详细信息,请参见以下问题:https://dev59.com/-mAg5IYBdhLWcg3wOo39 - Rémi
2
@HelloGoodbye:是的,它会导致运行时开销。还可以参考这个问题:https://dev59.com/-mAg5IYBdhLWcg3wOo39 - Rémi
1
如果静态变量在程序开始时被初始化,那么@463035818就不是一个数字。 - HelloGoodbye
显示剩余7条评论

66
一些来自C++标准的相关措辞:

3.6.2 Initialization of non-local objects [basic.start.init]

1

The storage for objects with static storage duration (basic.stc.static) shall be zero-initialized (dcl.init) before any other initialization takes place. Objects of POD types (basic.types) with static storage duration initialized with constant expressions (expr.const) shall be initialized before any dynamic initialization takes place. Objects of namespace scope with static storage duration defined in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit. [Note: dcl.init.aggr describes the order in which aggregate members are initialized. The initialization of local static objects is described in stmt.dcl. ]

[more text below adding more liberties for compiler writers]

6.7 Declaration statement [stmt.dcl]

...

4

The zero-initialization (dcl.init) of all local objects with static storage duration (basic.stc.static) is performed before any other initialization takes place. A local object of POD type (basic.types) with static storage duration initialized with constant-expressions is initialized before its block is first entered. An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (basic.start.init). Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the completion of its initialization. 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. If control re-enters the declaration (recursively) while the object is being initialized, the behavior is undefined. [Example:

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

--end example]

5

The destructor for a local object with static storage duration will be executed if and only if the variable was constructed. [Note: basic.start.term describes the order in which local objects with static storage duration are destroyed. ]


3
这个回答解决了我的问题,与被接受的回答不同,它没有依赖“个人经验”这样的内容。我特别在寻找构造函数中静态初始化本地静态对象的例外提到:如果初始化通过抛出异常退出,则初始化将不完全,因此下次控制进入声明时会再次尝试初始化。 - Bensge

32

所有静态变量的内存在程序加载时分配。但局部静态变量是在第一次使用时创建和初始化的,而不是在程序启动时。关于这个问题有一些很好的阅读资料,以及关于静态变量的一般性介绍,可以在这里找到。总的来说,我认为这些问题中的一些取决于具体的实现,尤其是如果你想知道这些东西在内存中的位置。


2
不完全准确,局部静态变量在程序加载时被分配和初始化为零(用引号括起来,因为这也不完全正确),然后在第一次进入它们所在的函数时重新初始化。 - Mooing Duck
看起来这个链接现在已经失效了,7年过去了。 - Steve
2
链接失效了。这里有一个存档:https://web.archive.org/web/20100328062506/http://www.acm.org/crossroads/xrds2-4/ovp.html - Eugene

10
编译器将在程序加载时分配定义在函数 foo 中的静态变量,但编译器还会向函数 foo 添加一些额外的指令(机器码),以便在首次调用该函数时初始化静态变量(例如调用构造函数,如果适用)。
@Adam: 编译器通过这种幕后注入代码的方式导致了你看到的结果。

8
我尝试测试来自Adam Pierce的代码,并添加了两个更多的案例:类中的静态变量和POD类型。我的编译器是g++ 4.8.1,在Windows操作系统(MinGW-32)上。 结果是类中的静态变量与全局变量相同对待。它的构造函数将在进入main函数之前被调用。
结论(适用于g ++,Windows环境):
1. 全局变量和类中的静态成员:构造函数在进入main函数之前被调用。 2. 局部静态变量:只有在第一次执行到其声明时才会调用构造函数。 3. 如果局部静态变量是POD类型,则它也会在进入main函数之前进行初始化。 POD类型示例:static int number = 10;
(1):正确的状态应该是:“在调用同一翻译单元中的任何函数之前”。但是,对于简单的示例,如下面的示例,这是main函数。
#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;
    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环境下测试过吗?

2
你是怎么得出nummain函数之前被初始化的结论的?请注意,如果初始化程序不仅仅是一个整数文字,那就不是这种情况:https://godbolt.org/z/Gasr7dTY8 很显然,在这个例子中,如果在main函数之前初始化了num,则预期的副作用将关闭。 - 463035818_is_not_a_number

4

还是在第一次调用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块内使用它。第一次调用时,变量将被初始化。然后,在第一次和下一次调用中,它的值将被返回(通过引用)。


3
静态变量被分配在代码段内——它们是可执行映像的一部分,因此已经初始化并映射。函数作用域内的静态变量也是如此处理,作用域纯粹是语言级别的构造。因此,保证静态变量将被初始化为0(除非您指定其他值),而不是未定义的值。还有一些其他方面的初始化可以利用——例如共享段允许同时运行的不同实例的可执行文件访问相同的静态变量。在C++(全局范围)中,静态对象的构造函数作为程序启动的一部分被调用,在C运行时库的控制下。至少在Visual C++中,可以通过init_seg pragma来控制对象初始化的顺序。

6
这个问题涉及到函数作用域静态变量。当它们具有非平凡的构造函数时,它们会在第一次进入函数时进行初始化。或者更具体地说,当达到那行代码时。 - Adam Mitz
正确——但问题涉及变量分配的空间,并使用简单数据类型。空间仍然在代码段中分配。 - Rob Walker
我不认为代码段与数据段在这里真的很重要。我认为我们需要从OP那里得到澄清。他确实说“如果适用,已初始化”。 - Adam Mitz
5
变量从不在代码段内分配;这样它们就不能被写入。 - botismarius
1
静态变量根据是否被初始化,分配在数据段或BSS段中的空间。 - EmptyData

-2
在下面的代码中,它打印出 Initial = 4,这是 static_x 的值,因为它在编译时实现。
 int func(int x)
    {
        static int static_x = 4;
        static_x = x;
        printf ("Address = 0x%x",&static_x );   // prints 0x40a010
        return static_x;
    }
    int main()
    {
        int x = 8;
        uint32_t *ptr = (uint32_t *)(0x40a010); // static_x location
        printf ("Initial = %d\n",*ptr);
        func(x);
    
        return 0;
    }

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