C++全局变量初始化顺序

37
我不理解下面代码示例的作用和实现方式:
#include <stdio.h>

int f();

int a = f(); // a exists just to call f

int x = 22;

int f() {
    ++x;
    return 123; // unimportant arbitrary number
}

int main() {
    printf("%d\n", x);
}

运行此代码将打印出23,这是直观的答案。

然而在C++中,全局变量应该按定义顺序初始化。这意味着a应该在x之前初始化,因为它在x之前被定义。如果是这样,那么函数f必须在x初始化之前调用,因为对f的调用是a定义的一部分。

如果确实在x初始化之前调用了f,那么f就会尝试增加x的值——其结果我并不确定(最可能是未定义行为或一些无意义的值)。然后,在初始化a之后,x将被初始化为22,程序将输出22

显然这不是发生的事情。但实际上它做了什么?

在评估a = f()之前,x明显被设置为22,但这意味着初始化顺序被颠倒了(我可能也对初始化或其发生时间有所错误理解)。
2个回答

39

问题有点微妙,请参考C++11 3.6.2了解详细信息。

对我们来说,重要的是“具有静态存储期的非局部变量”(或俗称的“全局变量”)有两个初始化阶段:静态初始化阶段和动态初始化阶段。首先是静态阶段,它看起来像这样:

int a = 0;
int x = 22;
动态初始化在此之后运行:
a = f();
重点在于静态初始化根本不会“运行” - 它只是设置在编译时已知的值,所以这些值在任何执行发生之前就已经设置好了。使初始化程序 int x = 22; 静态的原因是它是一个常量表达式。
有些情况下,动态初始化可能会被提升到静态阶段(但不一定),但这不是这种情况,因为它不符合以下要求:
  

动态初始化的版本在其初始化之前不会更改命名空间作用域内任何其他对象的值

当进行这种提升时,得到的初始值可以与未发生这种情况时不同。标准中有一个示例说明了这种“不确定”的初始化。

因此,初始化被分为两个部分:第一部分是不产生副作用的部分,它首先“运行”(没有函数,只有改变内存并增加堆栈指针的汇编代码),第二部分是产生副作用的部分,在其中实际的函数被执行。如果可以证明一个函数没有副作用,那么它可以被提升到第一部分。我理解得对吗?这很有道理。另外,你写了 x = f();,但我认为你的意思是 a = f();(这是我的代码中的内容)。 - corazza
你最后一段的第一句话有点奇怪:“当发生这种提升时,允许得到的初始值与没有发生时不同。” - 你是什么意思?看起来你漏掉了一个“它”,但总体上你所描述的有点令人困惑,也许举个例子会有帮助? - corazza
1
@ yannbane:例子在我引用的那一部分中。我不想开始复制它的大部分,因为你应该基本上读完它。我建议你[前往Github](https://github.com/cplusplus/draft)并获取标准的副本。是的,有两个阶段。第一阶段甚至不会“改变内存”-初始值只是写入二进制文件中,并由加载程序加载到内存中。 - Kerrek SB
1
@yannbane:我把示例粘贴在pastebin这里 - Kerrek SB
1
@yannbane:我修复了拼写错误,对此感到抱歉。 - Kerrek SB

4

另外,考虑以下内容:

#include <iostream>
using namespace std;

int f();
int g();

int a = f();
int b = g();

int main() {
        cout << a << " " << b;
}

int f() {
        b++;
        cout << "f" << endl;
        return 1;
}

int g() {
        cout << "g" << endl;
        return 2;
}

输出结果为:
f
g
1 2

b = g();替换为b = 22;会导致打印出1 23。Kerrek SB的答案解释了这是为什么。

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