全局变量何时分配内存?

9
有一段时间以来,我一直被困扰着,但是我没有找到任何有关这个问题的好资源。我的代码中有一些全局变量。很明显它们是按某种顺序初始化的,但是在任何初始化发生之前,为所有这些对象保留内存吗?
下面是一个简单的示例,说明了我的代码可能出现的问题以及我如何使用答案:
我有一个map<RTTI, object*>,其中包含我的代码中每个类的示例,我用它来从文件中加载对象。为了创建这些示例,我使用一些全局变量来向objectPool引入类实例。但有时这些示例实例会在ObjectPool本身之前初始化。这将生成运行时错误。
为了修复该错误,我使用了一些延迟初始化器map<RTTI,object*>* lateInitializedObjectPool;。现在每个实例首先检查是否已经初始化objectPool,如果没有,则进行初始化,然后将其引入对象池。看起来工作得很好,但我担心即使在其他类开始引入自己之前,也没有为对象池指针保留所需的内存,这可能会导致访问冲突。

2
@Violet:不对...栈通常只用于本地变量,尽管我猜实现可以自由地做一些不寻常的事情... - Tony Delroy
3
@Violet:我非常确定全局对象不会从堆栈中获取内存。实际上,我知道至少有一个编译器平台肯定不会这样做,如果有很多这样的平台,我会感到惊讶。 - Nicol Bolas
1
全局对象是静态分配的,即在加载时分配。初始化是一个不同的、更高级的问题,请参见答案。 - Kerrek SB
1
有一个普遍的误解,即全局变量是在堆栈中分配的,而我看到这已经被澄清了,一个简单的思考方式使其易于记忆,即程序中的每个线程都有一个不同的堆栈,但全局变量(我不是在谈论线程本地存储)在不同的线程之间共享。如果您记住不同的线程具有不同的堆栈,那么全局变量不能被堆栈分配就是自然的结果。 - David Rodríguez - dribeas
另一种更简单的方法是:尝试在主类内外分配一个大数组。例如,int array[100000]。你可以很容易地看出当定义在函数内部时(意味着它被分配在堆栈中),你会得到堆栈溢出或类似的运行时错误,但当它声明在函数范围之外时,你就不会遇到这些问题。 - Ali1S232
显示剩余2条评论
3个回答

7

在命名空间范围声明的变量(与在类或函数中声明的变量相对)的对象本身的空间(sizeof(ObjectType))是由可执行文件(或DLL)加载器分配的。如果对象是一个使用聚合初始化的POD,那么它通常通过链接器直接将这些值写入可执行文件并且exe的加载器仅将所有这些内容一起传送到内存中来设置其值。不使用聚合初始化的对象最初值为零。

在完成上述操作之后,如果任何这些对象具有构造函数,则在运行main之前执行这些构造函数。 因此,如果其中任何构造函数动态分配内存,就会在可执行文件加载后但在运行main之前进行分配。


所以,如果我理解你的回答正确的话,你的意思是一个程序有三个阶段:全局变量的内存初始化 -> premain函数调用 -> main函数调用?这意味着所有的premain函数调用都发生在所有静态内存分配之后? - Ali1S232
@Gajet:是的,但请记住:仅因为分配了内存并不意味着这些对象已被初始化(构造)。在它们获得构造函数之前,它们只是无用的内存。如果它们是POD,则没问题,但如果它们是具有需要调用的构造函数的实际对象,则不能触及它们。全局对象初始化的顺序是未定义的。 - Nicol Bolas
我只想补充一点,即 main 函数之前构造函数的调用顺序是未定义的,这可能会导致各种奇妙的错误(http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14)。 - Gilad Naor
@Gilad 构造函数调用的顺序在单个翻译单元中定义。但是,翻译单元的顺序没有指定。 - James Kanze

3

编译器通常会为变量分配不同的内存区域:

  • 最初计算出来的变量通常包含所有0,可能会在main()函数之前运行一个预构造函数以更改它们的内容
  • 预定的具有特定非零值,使得它们可以以预先构建的形式写入可执行映像并进行页面错误处理以供使用。

当我说“单独的内存区域”时,我的意思是操作系统可执行文件加载器为进程安排的一些内存,就像堆栈或堆一样,但不同的是这些区域具有固定的预定大小。 在UNIX中,上述所有0的内存区域通常称为“BSS”,非0初始化区域称为“数据” - 有关详细信息,请参见http://en.wikipedia.org/wiki/Data_segment


1

C++有“静态存储期”的概念。这指的是在程序执行期间将占用固定空间的所有变量类型,不仅包括全局变量,还包括命名空间、类和函数级别的static变量。

请注意,在所有情况下,内存分配都可以在main之前完成,但实际初始化方式不同。此外,其中一些在正常初始化之前会被初始化为零。具体发生的方式未指定:编译器可能会添加隐藏的函数调用,或者操作系统只是偶然地将进程空间清零等。


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