25

我正在从一本书中阅读有关 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?还是没有初始化?
4

4 回答 4

17

C++标准的3.6.2“非本地对象的初始化”中给出了初始化步骤:

步骤 1:在任何其他初始化发生之前进行零初始化xy

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

第三步:另一个变量会被动态初始化,获取值2

于 2010-06-14T07:43:14.260 回答
15

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

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

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

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

于 2010-06-14T11:34:34.510 回答
2

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

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

我认为最可能的情况是:

  1. 为变量分配了空间,它们的值都为 0。
  2. 一个变量,比如 x,被初始化并设置为值 1。
  3. 另一个,比如 y,被初始化并设置为值 2。

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

于 2010-06-14T07:11:43.977 回答
2

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

于 2010-06-14T07:11:53.987 回答