我在一本书中读到有关 SIOF 的内容,它给出了一个例子:

//file1.cpp
extern int y;
int x=y+1;

//file2.cpp
extern int x;
int y=x+1;  

现在我的问题是:
在上面的代码中,会发生以下情况吗?

  1. 在编译 file1.cpp 时,编译器将 y 保留原样,即不为其分配存储空间。
  2. 编译器为 x 分配存储空间,但不对其进行初始化。
  3. 在编译 file2.cpp 时,编译器将 x 保留原样,即不为其分配存储空间。
  4. 编译器为 y 分配存储空间,但不初始化它。
  5. 在链接 file1.o 和 file2.o 时,现在先初始化 file2.o,所以现在:
    x 的初始值是否为 0?或者没有被初始化?
有帮助吗?

解决方案

初始化步骤在C++标准的3.6.2“非局部对象的初始化”中给出:

步骤1: xy 在任何其他初始化发生之前都被零初始化。

第2步: x 或者 y 是动态初始化的 - 标准未指定哪一个。该变量将获得值 1 因为另一个变量将被零初始化。

步骤3:另一个变量将被动态初始化,获取值 2.

其他提示

SIOF 在很大程度上是一个运行时工件,编译器和链接器与它没有太多关系。考虑 atexit() 函数,它注册要在程序退出时调用的函数。许多 CRT 实现都有类似的程序初始化功能,我们称之为 atinit()。

初始化这些全局变量需要执行代码,其值无法由编译器确定。因此,编译器会生成执行表达式并分配值的机器代码片段。这些代码片段需要在 main() 运行之前执行。

这就是 atinit() 发挥作用的地方。常见的 CRT 实现会遍历 atinit 函数指针列表并按顺序执行初始化片段。问题在于函数在 atinit() 列表中注册的顺序。虽然 atexit() 具有明确定义的 LIFO 顺序,并且它是由代码调用 atexit() 的顺序隐式确定的,但 atinit 函数的情况并非如此。语言规范不需要顺序,您无法在代码中执行任何操作来指定顺序。SIOF 就是结果。

一种可能的实现是编译器在单独的部分中发出函数指针。链接器将它们合并,生成 atinit 列表。如果您的编译器这样做,那么初始化顺序将由您链接目标文件的顺序确定。查看映射文件,如果您的编译器执行此操作,您应该会看到 atinit 部分。它不会被称为 atinit,但可能是某种带有“init”的名称。查看调用 main() 的 CRT 源代码也应该能提供一些见解。

整个要点(以及它被称为“惨败”的原因)是不可能肯定地说 什么 会发生这样的情况。本质上,您要求的是不可能的事情(两个变量都大于另一个)。既然他们不能这样做,他们会做什么就存在一些问题——他们可能会产生 0/1、或 1/0、或 1/2、或 2/1,或者可能(最好的情况)只是一个错误信息。

它依赖于编译器,并且可能依赖于运行时。当访问文件中的第一个变量或访问每个变量时,编译器可能决定延迟初始化静态变量。否则,它将在启动时按文件初始化所有静态变量,其顺序通常取决于文件的链接顺序。文件顺序可能会根据依赖性或其他依赖于编译器的影响而改变。

静态变量通常初始化为零,除非它们具有常量初始值设定项。同样,这取决于编译器。因此,当其中一个变量初始化时,另一个变量可能为零。但是,由于两者都有初始值设定项,某些编译器可能会保留这些值未定义。

我认为最有可能的情况是:

  1. 为变量分配了空间,并且值都为 0。
  2. 一个变量(例如 x)被初始化并设置为值 1。
  3. 另一个(例如 y)被初始化并设置为值 2。

你可以随时运行它并查看。某些编译器可能会生成进入无限循环的代码。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top