静态变量和全局变量何时被初始化?

43

C++ 中,我知道 静态全局 对象会在 main 函数之前构造。但是你知道,在 C 中,不存在这种 main 函数之前的 初始化过程

例如,在我的代码中:

int global_int1 = 5;
int global_int2;
static int static_int1 = 4;
static int static_int2;
  • 这四个变量何时被初始化
  • 编译期间像54这样的初始化数值存储在哪里?如何在初始化时管理它们?

类似的有趣讨论在这里: https://dev59.com/2XNA5IYBdhLWcg3wmfEa?rq=1 - Anand Rathi
2
我知道在C语言中,在main函数之前有一个初始化过程,因为C规范中有说明(详见C99 5.1.2/1)。 - Mike Seymour
作为程序员,除了定义变量并在需要时提供初始化程序之外,您无需再做任何操作。请问“如何在初始化时管理它们”这句话是什么意思?能否请您解释一下? - Mike Seymour
@MikeSeymour,《C99 5.1.2/1》确实指出:“所有具有静态存储期的对象在程序启动前都应该被初始化(设置为它们的初始值)。这种初始化的方式和时间是未指定的。”但与gcc C++不同,它并没有真正定义一个名为_start的函数。 - Zachary
@MikeSeymour,C语言只允许C++称之为静态初始化的操作。虽然C++允许在进入“main”函数后进行动态初始化,但所有现有的编译器都会在“main”函数之前初始化静态变量,并且有相当大量的现有代码依赖于此。 - James Kanze
显示剩余4条评论
4个回答

29

我推测你所说的静态和全局对象是指在命名空间作用域下定义的具有静态生命周期的对象。当这些对象在局部作用域下定义时,规则略有不同。

C++正式地将这样的变量初始化分为三个阶段: 1. 零初始化 2. 静态初始化 3. 动态初始化 语言还区分需要动态初始化和需要静态初始化的变量:所有静态对象(具有静态生命周期的对象)首先进行零初始化,然后进行静态初始化,最后进行动态初始化。

简单来说,动态初始化意味着必须执行某些代码;通常情况下,静态初始化不需要执行代码。因此:

extern int f();

int g1 = 42;    //  static initialization
int g2 = f();   //  dynamic initialization

另一个近似的方式是静态初始化是C支持的(对于具有静态生存期的变量),动态初始化则适用于其他一切。

编译器如何执行这个操作当然取决于初始化方式,在基于磁盘的系统中,可执行文件从磁盘加载到内存中时,静态初始化的值是磁盘镜像的一部分,并由系统直接从磁盘加载。在经典的Unix系统上,全局变量将分为三个“段”:

文本:
代码,加载到写保护区域。带有const类型的静态变量也将放置在此处。
数据:
具有静态初始化程序的静态变量。
bss:
没有初始化程序的静态变量(C和C ++)或具有动态初始化程序(C ++)。可执行文件不包含此段的映像,系统只需在启动代码之前将其全部设置为0。

我怀疑许多现代系统仍然使用类似的方法。

编辑:

另外一点说明:以上内容是针对C++03的。对于现有程序,C++11可能不会改变任何东西,但它确实添加了constexpr(这意味着某些用户定义的函数仍然可以是静态初始化)和线程本地变量,这就开启了一整个新的问题。


谢谢James。你的回复真是太棒了。在最后一段中,你提到动态初始化位于.bss段中。为什么呢?直觉上,我认为它应该位于.data段中,因为它被初始化了。 - Zachary
再次打扰您。如您所知,从您的回答中,我有了更好的理解。但是我不知道背后的原理。这些语言规则是如何推导出来的?您能给我一些参考资料吗?这里是一个链接 存储类别说明符。阅读完这个页面后,我更加困惑了。有太多小细节需要考虑。 - Zachary
@Zack 动态初始化直到程序开始执行时才会完成。就可执行文件的加载而言,它是零初始化的,仅此而已。 - James Kanze
@Zack 这些规则在某种程度上是历史条件的。C语言没有动态初始化,因此将两种初始化分开以节省磁盘空间:操作系统将程序分成三个段,并用0填充那些没有磁盘映像的段。C语言声明所有没有初始值的静态变量都将被初始化为0,因为这就是系统所做的。C++取消了初始值必须是静态的限制,但当它们是静态的时候,仍然像C语言一样行事。 - James Kanze
1
James,你提到“所有静态对象(具有静态生命周期的对象)首先进行零初始化,然后进行静态初始化”。你是指静态生命周期对象将被初始化两次吗?如果它是一个对象,那么构造函数将被调用两次。但在实际情况下,这并不是真的。 - Zachary
1
@Zack 理论上是可以的。但由于静态初始化中从不涉及用户编写的代码,因此您无法检测到其是否已完成初始化,在实践中,所有实现都会将其跳过。但如果初始化不是静态的,则可以检测到双重初始化。例如,如果有类似于 int i = f(); 的语句,则可能在某些静态对象的构造函数中将 i 视为 0,而在其他情况下则视为 f() 的返回值。 - James Kanze

19

前言:在C++中,“static”一词有许多不同的含义。不要混淆。

您的所有对象都具有静态存储期,因为它们既不是自动的也不是动态的。(虽然线程局部变量有点像static)

在C++中,静态对象的初始化分为两个阶段:静态初始化和动态初始化。

  • 动态初始化需要实际执行代码,因此对于以构造函数调用开头的对象或其中初始化程序是仅能在运行时评估的表达式的对象会进行动态初始化。

  • 静态初始化是指已知初始化程序且不需要运行构造函数的初始化。(静态初始化要么是零初始化,要么是常量初始化)这适用于带有常量初始化程序的int变量,并且保证这些变量确实在静态阶段进行初始化。

  • (动态初始化的静态存储变量也会在其他任何操作发生之前静态零初始化。)

关键点在于静态初始化阶段根本不会“运行”。数据从一开始就存在。这意味着静态初始化没有任何“排序”或任何其他此类动态属性。如果您愿意,初始值已硬编码到您的程序二进制文件中。


静态对象有构造函数,何时调用构造函数(动态初始化)?我在主函数之前测试它,是否有相关的标准和信息? - daohu527

5
当这四个变量被初始化?
正如你所说,这发生在程序启动之前,即在main开始之前。C没有进一步指定;在C++中,这些发生在静态初始化阶段,在构造函数或初始化器更复杂的对象之前。
像5和4这样的初始化值在编译期间存储在哪里?
通常,非零值存储在程序文件的数据段中,而零值存储在bss段中,该段只保留足够的内存用于变量。当程序启动时,数据段加载到内存中,bss段设置为零。(当然,语言标准没有指定这一点,因此编译器可以做其他事情,比如在运行main之前生成初始化每个变量的代码)。

非常感谢,特别是第二个问题。变量实际上是一个寻址的内存单元。因此,5只是存储在为其分配的.data部分/段中的内存单元中。 - Zachary
我在我的第二个问题的帖子中已经解释过了。Mike,你认为初始化是在链接期间完成的吗?因为在链接过程之后,程序的段基本上是固定的! - Zachary
(当然,编程语言标准并没有规定这一点,因此编译器可以执行其他操作,例如在运行 main 函数之前生成代码来初始化每个变量。)我只想了解这个问题,它是在程序加载时就被执行了吗? - daohu527

3

从标准中改写:

所有没有动态存储期、没有线程本地存储期且不是局部变量的变量都具有静态存储期。换句话说,所有全局变量都具有静态存储期。

具有动态初始化的静态对象不一定在主函数的第一个语句之前创建。这是实现定义的,即这些对象是在 main 函数的第一个语句之前创建,还是在与要初始化的静态变量定义在同一翻译单元中的任何函数或变量的第一次使用之前创建。

因此,在您的代码中,global_int1 和 static_int1 由于被静态初始化,所以它们肯定会在 main 函数的第一个语句之前初始化。然而,global_int2 和 static_int2 是动态初始化的,因此它们的初始化根据我上面提到的规则是实现定义的。

至于您的第二点,我不确定我理解您的意思。能否请您澄清一下?


“这些对象是在 main 函数的第一条语句之前创建,还是在与静态变量初始化相同的翻译单元中定义的任何函数或变量的第一次使用之前创建,这是实现定义的。” 无论如何,在某个翻译单元中声明 std::string s = "abc"; 并稍后从不同的 TU(或同一个 TU)访问它总是安全的。保证在访问时对象已被初始化。正确吗? - Aviv Cohn

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