93

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

int globalgarbage;
unsigned int anumber = 42;

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

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

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

4

8 回答 8

96

我对此很好奇,所以我编写了以下测试程序并使用 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
于 2008-09-11T00:18:08.460 回答
60

C++ 标准中的一些相关措辞:

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

1

在进行任何其他初始化之前,具有静态存储持续时间 ( basic.stc.static ) 的对象的存储应进行零初始化 ( dcl.init )。使用常量表达式 ( expr.const )初始化的具有静态存储持续时间的 POD 类型 ( basic.types ) 的对象应在任何动态初始化发生之前进行初始化。在同一翻译单元中定义并动态初始化的具有静态存储持续时间的命名空间范围的对象应按照其定义在翻译单元中出现的顺序进行初始化。[注: 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 描述了具有静态存储持续时间的本地对象被销毁的顺序。]

于 2008-09-12T12:20:09.697 回答
28

所有静态变量的内存都是在程序加载时分配的。但是局部静态变量是在第一次使用时创建和初始化的,而不是在程序启动时。有一些关于它的好读物,以及一般的静力学,here。一般来说,我认为其中一些问题取决于实现,特别是如果你想知道这些东西在内存中的位置。

于 2008-09-11T00:41:46.857 回答
10

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

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

于 2008-09-11T01:00:58.823 回答
6

我尝试再次测试Adam Pierce的代码并添加了另外两个案例:类中的静态变量和 POD 类型。我的编译器是 g++ 4.8.1,在 Windows 操作系统(MinGW-32)中。结果是类中的静态变量与全局变量相同。它的构造函数将在进入主函数之前被调用。

  • 结论(针对g++,Windows环境):
  1. 类中的全局变量静态成员:在进入函数(1)之前调用构造函数。
  2. 局部静态变量:仅在执行第一次到达其声明时才调用构造函数。
  3. 如果局部静态变量是 POD 类型,那么在进入main函数(1)之前它也会被初始化。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 环境中测试过吗?

于 2013-12-03T15:49:16.380 回答
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 块中使用它。在第一次调用时,变量将被初始化。然后,在第一次和下一次调用时,将返回其值(通过引用)。

于 2008-09-11T05:58:41.620 回答
3

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

函数范围内的静态变量的处理方式相同,范围纯粹是语言级别的构造。

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

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

在 C++(全局范围)中,静态对象的构造函数作为程序启动的一部分在 C 运行时库的控制下被调用。在 Visual C++ 下,至少初始化对象的顺序可以由init_seg pragma 控制。

于 2008-09-10T23:54:27.237 回答
-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;
    }
于 2022-01-24T16:36:11.477 回答