静态(全局)对象的初始化是如何发生的?

5
我正在试图弄清楚全局对象的构造函数如何被调用。我知道它们在翻译单元中的任何内容被使用之前就被调用了,这个我没问题。我想知道在Linux和Windows(x86和x64)中是如何实现的。
我记得Windows(x86)使用一个链接列表来进行构造和销毁,但我很难找到有关此事的任何资源。
我已经找到了以下有关相关主题的资料,但没有什么涵盖我所寻找的内容。 还有PE文件格式文档。
请问有人可以指点我正确的方向找到这些信息吗?

2
不看规范,我怀疑这个实现完全依赖于编译器,而不是操作系统。 - Joachim Isaksson
你是出于好奇还是因为有一个(工作/项目相关的)问题,你认为通过理解这个问题可以解决? - TobiMcNamobi
我将要做一个关于全局变量的构造/析构的演示。作为其中的一部分,我想比较和对比标准保证和实现实际执行的内容。 - Graznarak
@Joachim Isaksson:这不是与链接器有关而非编译器有关吗? - abRao
我不知道。这可能取决于操作系统或编译器。PE文件有一个入口点,在模块加载(或链接)时执行。通过这种方式实现全局初始化是可能的,但我不确定是否是这样实现的。 - Graznarak
2个回答

1

如果您还没有理解,我这里有代码来演示。 SourceA.cpp

#include "stdafx.h"

extern bool DoFunctionB();

class MyClassA {
protected:
    bool bIsInitialized;
    bool bIsBInitialized;
public:
    MyClassA () : bIsInitialized(true) {
        bIsBInitialized = DoFunctionB();
    }

    bool IsInitialized() {
        return bIsInitialized;
    }
};


static MyClassA MyClassGlobal;

bool DoFunctionA() {
    return MyClassGlobal.IsInitialized();
}

SourceB.cpp

#include "stdafx.h"

extern bool DoFunctionA();

class MyClassB {
protected:
    bool bIsInitialized;
    bool bIsAInitialized;
public:
    MyClassB () : bIsInitialized(true) {
        bIsAInitialized = DoFunctionA();
    }

    bool IsInitialized() {
        return bIsInitialized;
    }
};


static MyClassB MyClassGlobal;

bool DoFunctionB() {
    return MyClassGlobal.IsInitialized();
}

Main.cpp

#include "stdafx.h"

extern bool DoFunctionA();
extern bool DoFunctionB();

int _tmain(int argc, _TCHAR* argv[])
{
    bool a = DoFunctionA();
    bool b = DoFunctionB();
    return 0;
}

将这些添加到新的Windows控制台应用程序中。在构造函数和DoFunctionX()代码中放置断点。按F11并逐步执行它。您会发现,无论哪个全局初始化程序首先被调用,都会在初始化该文件中的静态对象之前使用另一个cpp文件中的DoFunction。

不管您认为标准是什么。这就是编译器所做的。这是您必须关注的危险。
如果在构造函数中向上堆栈2步,您将看到我已经告诉过您的指针列表。
快乐编程。

0

你错了,认为全局构造函数必须在对象使用之前运行。我已经根据这个假设修复了许多错误,但它根本不是真的。对于gcc、MSVC和XCode都是如此。

你可以在gcc中指定一个属性((init_priority(X)))来强制排序,

或者在msvc中使用#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )。

在使用XCode时,初始化代码按照传递给链接器的目标文件的顺序运行。

我认为没有标准,如果有的话,很少有人遵循它。这取决于工具创建者决定如何跟踪初始化和何时进行初始化。


标准仅规定翻译单元中的所有全局对象将在使用翻译单元中的任何内容(来自外部代码)之前进行初始化。我知道处理全局对象初始化所涉及的困难,但这不是我关注的重点。我关注的是编译器或ABI如何实现调用全局对象的构造函数和析构函数。 - Graznarak
全局对象在main函数之前被初始化。你引用的是哪个标准?没有编译器支持这个论点。创建2个翻译单元。在头文件中添加2个类声明,并导出该类的静态全局变量,每个单元1个。在.cpp文件中添加构造函数代码,引用其他单元的全局静态对象,并调用其方法。编译、运行并查看哪个在初始化之前被使用。 - Dan
顺便说一下,在构造函数中设置一个断点,沿着堆栈向上走,看看它们是如何初始化的。每个翻译单元都有一个列表,这些列表会被连接在一起,然后进行初始化。初始化优先级可以让你控制列表中的元素出现的位置。 - Dan
Dan: 在 ISO/IEC 14882:2011 中的 3.6.2/4 中指出:“非局部静态存储变量的动态初始化是否在 main 函数的第一条语句之前完成是由实现定义的。如果初始化被延迟到 main 函数的第一条语句之后的某个时间点,则它应该在与要初始化的变量相同的翻译单元中定义的任何函数或变量的第一个 odr-use(3.2)之前发生。” - Graznarak
如果主函数不在该模块中,那么你认为在那里可以找到做任何事情的内容呢?肯定没有从该单元外部调用的东西。这与我所说的并无不同,它的意思是如果你在 main 函数之前没有初始化,只要在 main 函数使用之前进行初始化就可以了。除了全局初始化以外,没有任何东西会执行除 main 函数以外的任何操作。没有任何东西会被用于初始化以外的地方,而这些东西都起源于 main 函数。 - Dan

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