什么时候使用外部链接初始化全局常量可以避免静态初始化顺序失败?
When is initialization of global constants with external linkage safe from static initialization order fiasco?
考虑以下示例:
tt.h 声明一个带有外部链接的全局常量 extern int g_TRAGIC;
tt.cpp定义g_TRAGIC如下const int g_TRAGIC = 0xF001;
my.cpp想用它来定义自己的全局常量const int g_MAGIC = g_TRAGIC;
当我阅读 iso-FAQ 时,我认为这会导致静态初始化顺序失败。但是 iso-FAQ 注释
The static initialization order fiasco can also, in some cases, apply to built-in/intrinsic types.
一些 个案例是什么意思?在什么条件下,我们从 SIOF 中保存并发声 built-in/intrinsic 类型,特别是常量?或者必须将 Construct On First Use Idiom 用于具有外部链接的 all 常量?
注意:在实际代码中我不能改变g_TRAGIC的定义。
进一步阅读iso-FAQ给我们一个答案。
如果您尝试通过常量函数的 return 值初始化 builtin/intrinsic 类型,则会发生 SIOF。
const int g_MAGIC = f(g_TRAGIC);
编译器可以生成不同种类的代码。
静态初始化数据段
编译器将名称及其初始值发送到数据部分。
.data
dw myData 6
这在编译时初始化,并在程序的整个生命周期中安全定义
构造数据
另一种方法是让编译器为变量保留一些space,并为数据创建一个initializer/constructor,然后在main
之前调用构造函数。执行析构函数(如果需要)atexit
.
class CriticalSection {
CRITICAL_SECTION m_myCS;
public:
CriticalSection() {
InitializeCriticalSection( &m_myCS );
}
~CriticalSection() {
DeleteCriticalSection( & m_myCS );
}
} cs;
合并
有些数据可能会在两个阶段执行。
struct Data {
bool initialized;
void *(*pMalloc)( size_t size );
} FixMalloc = { true, MyMalloc };
我见过编译器 (VS2013) 生成的代码在静态数据中将 initialized
初始化为 true,但创建一个函数以在运行时将 pMalloc
分配给 MyMalloc
。 (这是因为 MyMalloc 没有已知常量。)
单例方法
SomeClass * GetSomeClass()
{
static SomeClass cls;
return &cls;
}
这是顺序定义的 - 当它被调用时,但需要一个完整的 C++11
编译器才能保证线程安全。
总结
保证是:-
- 同一编译单元中的静态变量从上到下初始化。
- 静态初始化为单线程。
- 单例已定义构造顺序。但不一定是线程安全的。
保证不是 :-
- 同时初始化所有对象。
- 静态初始化有一个运行时。
在调用 main 之前,您的静态和 C/C++ 运行时都在引导。您的代码和运行时之间也会出现构造顺序问题,因此您可能会构造一些具有复杂构造函数的项目,这些构造函数依赖于不可用的服务。
考虑以下示例:
tt.h 声明一个带有外部链接的全局常量
extern int g_TRAGIC;
tt.cpp定义g_TRAGIC如下
const int g_TRAGIC = 0xF001;
my.cpp想用它来定义自己的全局常量
const int g_MAGIC = g_TRAGIC;
当我阅读 iso-FAQ 时,我认为这会导致静态初始化顺序失败。但是 iso-FAQ 注释
The static initialization order fiasco can also, in some cases, apply to built-in/intrinsic types.
一些 个案例是什么意思?在什么条件下,我们从 SIOF 中保存并发声 built-in/intrinsic 类型,特别是常量?或者必须将 Construct On First Use Idiom 用于具有外部链接的 all 常量?
注意:在实际代码中我不能改变g_TRAGIC的定义。
进一步阅读iso-FAQ给我们一个答案。
如果您尝试通过常量函数的 return 值初始化 builtin/intrinsic 类型,则会发生 SIOF。
const int g_MAGIC = f(g_TRAGIC);
编译器可以生成不同种类的代码。
静态初始化数据段
编译器将名称及其初始值发送到数据部分。
.data
dw myData 6
这在编译时初始化,并在程序的整个生命周期中安全定义
构造数据
另一种方法是让编译器为变量保留一些space,并为数据创建一个initializer/constructor,然后在main
之前调用构造函数。执行析构函数(如果需要)atexit
.
class CriticalSection {
CRITICAL_SECTION m_myCS;
public:
CriticalSection() {
InitializeCriticalSection( &m_myCS );
}
~CriticalSection() {
DeleteCriticalSection( & m_myCS );
}
} cs;
合并
有些数据可能会在两个阶段执行。
struct Data {
bool initialized;
void *(*pMalloc)( size_t size );
} FixMalloc = { true, MyMalloc };
我见过编译器 (VS2013) 生成的代码在静态数据中将 initialized
初始化为 true,但创建一个函数以在运行时将 pMalloc
分配给 MyMalloc
。 (这是因为 MyMalloc 没有已知常量。)
单例方法
SomeClass * GetSomeClass()
{
static SomeClass cls;
return &cls;
}
这是顺序定义的 - 当它被调用时,但需要一个完整的 C++11
编译器才能保证线程安全。
总结
保证是:-
- 同一编译单元中的静态变量从上到下初始化。
- 静态初始化为单线程。
- 单例已定义构造顺序。但不一定是线程安全的。
保证不是 :-
- 同时初始化所有对象。
- 静态初始化有一个运行时。
在调用 main 之前,您的静态和 C/C++ 运行时都在引导。您的代码和运行时之间也会出现构造顺序问题,因此您可能会构造一些具有复杂构造函数的项目,这些构造函数依赖于不可用的服务。