静态初始化命令fiasco

时间:2010-06-14 06:47:26

标签: c++ initialization dependencies static-order-fiasco

我正在读一本关于SIOF的书,并举了一个例子:

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

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

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

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

4 个答案:

答案 0 :(得分:11)

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

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

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

步骤3:动态初始化另一个变量,得到值2

答案 1 :(得分:11)

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

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

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

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

答案 2 :(得分:2)

完整的观点(以及它被称为“惨败”的原因)是,在这样的情况下,不可能确切地说会发生什么。从本质上讲,你要求的是不可能的事情(两个变量各自比另一个更大)。由于他们无法做到这一点,他们将会做些什么 - 他们可能产生0/1,或1/0,或1/2,或2/1,或可能(最好的情况)只是一个错误消息。

答案 3 :(得分:1)

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

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

我认为最可能出现的情况是:

  1. 为变量分配空间,两者都具有值0。
  2. 一个变量,比如x,被初始化并设置为值1。
  3. 另一个,比如y,被初始化并设置为值2。
  4. 您可以随时运行并查看。可能是某些编译器会生成进入无限循环的代码。