如何计算静态对象销毁顺序
How static object destruction order is calculated?
该标准要求实现执行以下操作:
3.6.3.1 如果构造函数的完成或动态初始化 具有静态存储持续时间的对象在 另一个,第二个析构函数的完成是有序的 在启动第一个析构函数之前。
以下演示演示了这一点:
struct A
{
A(int i) :i(i) {}
~A() { std::cout << "destruct A(" << i << ")n"; }
int i;
};
void f1() { static A a(1); }
void f2() { static A a(2); }
int main(int argc, char* argv[]) {
if (argc <= 1) {
std::cout << "f1/f2n";
f1();
f2();
} else {
std::cout << "f2/f1n";
f2();
f1();
}
return 0;
}
问题是:实施如何能够遵守?如何跟踪每个建筑?
斯坦纳德说了什么,建议了什么?
我们先来看看exit()
定义
18.5/8:首先,销毁具有线程存储持续时间并与当前线程关联的对象。接下来,具有静态的对象 存储持续时间被销毁,函数通过调用注册 atexit [脚注 221:每次注册函数时都会调用该函数。
然后上面一点我们提醒:
18.5/5:atexit() 函数注册 f 指向的函数,以便在正常程序终止时不带参数调用。
然后查看 3.6.3/3 中终止的操作顺序,我们发现在终止时,向 atexit()
注册的函数以与其注册相反的顺序调用。 这听起来是不是很熟悉?还有更多 !该标准保证了对析构函数的调用和对注册到atexit
函数的调用的顺序也以相反的顺序进行。
因此,严格来说,该标准并没有说静态析构函数是使用 atexit 函数管理的,但它表明存在非常强大的联系。 这当然是为什么许多C++实现使用 atexit 机制来破坏静态的原因。
如何为特定实现演示这一点?
在您的示例中,很难将编译器专门为静态销毁生成的代码与终止序列中生成的其他典型代码分开。我向您推荐以下经验:
更改代码以在不同的标头中定义结构,并将成员函数的实现放在不同的文件中。 然后将一个简单的 cpp 文件添加到您的项目中,该文件仅包含全局(即静态存储)变量的定义:
#include "Header.h" // our declaration for A without implementation
A a(3);
使用汇编器输出编译所有代码。 由于在这个编译单元中,只有与 A 的一个实例的构造、初始化和销毁相关的代码,因此很容易理解。
在MSVC 2013中,有初始化代码(我添加的评论):
??__Ea@@YAXXZ PROC ; `dynamic initializer for 'a'', COMDAT
; 3 : A a(3);
...
push 3 ; parameter for the intialisation
mov ecx, OFFSET ?a@@3UA@@A ; adress of a
call ??0A@@QAE@H@Z ; call to constructor A::A
为此初始化生成的代码紧跟以下内容(注释来自 MSVC):
push OFFSET ??__Fa@@YAXXZ ; `dynamic atexit destructor for 'a''
call _atexit
所以这里写得很清楚! 编译器生成对atexit()
的调用,该调用注册生成的函数,即此特定变量的"动态退出析构函数"。 此函数在汇编程序代码的其他位置定义:
??__Fa@@YAXXZ PROC ; `dynamic atexit destructor for 'a'', COMDAT
...
mov ecx, OFFSET ?a@@3UA@@A ; a ===> tell which object
call ??1A@@QAE@XZ ; A::~A ===> and tell to call destructor
...
而且这个基本的编译单元中几乎没有其他代码。
带有-std=c++11
的 GCC 4.8.1 会产生以下f1
:
push %rbp
mov %rsp,%rbp
mov $0x6021c0,%eax
movzbl (%rax),%eax
test %al,%al
jne 0x400a1d <f1()+80>
mov $0x6021c0,%edi
callq 0x400830 <__cxa_guard_acquire@plt>
test %eax,%eax
setne %al
test %al,%al
je 0x400a1d <f1()+80>
mov $0x1,%esi
mov $0x6021d0,%edi
callq 0x400b3a <A::A(int)>
mov $0x6021c0,%edi
callq 0x4008a0 <__cxa_guard_release@plt>
mov $0x602080,%edx
mov $0x6021d0,%esi
mov $0x400b50,%edi
callq 0x400870 <__cxa_atexit@plt>
mov 0x2017ad(%rip),%eax # 0x6021d0 <_ZZ2f1vE1a>
pop %rbp
retq
在a
的线程安全构造之后,调用__cxa_atexit
; %edi
指向A
的解构器,%esi
保存a
的地址。退出后,实现使用给定参数调用给定函数,(在这种情况下,它在析构函数中充当this
)以相反的顺序。
- CMake-按正确顺序将项目与C运行时对象文件链接
- 具有包含其他对象的类的对象创建顺序
- 按类成员的顺序对包含类对象的C++向量进行排序
- 如果在 C++ 构造函数中以错误的顺序初始化对象数据,会发生什么类型的错误
- QML 对象的销毁顺序
- 构造函数中没有参数的对象类成员按什么顺序初始化?
- 如何更改数组中2个对象的顺序
- 对象指针打印结果以相反的顺序进行
- 在C++中,当表达式涉及对象时,将表达式赋值到对象中时,是否有定义的操作顺序?
- 用QT形式顺序命名的UI对象填充数组
- 如何巩固 Bazel 构建对象的顺序
- 使用相同的文件对象按顺序写入和读取文件
- 共享库中静态对象的销毁顺序
- 当涉及单身对象时,克服静态初始化顺序惨败
- 对同一对象进行评估的链接操作和顺序
- 我可以按任意顺序将对象堆叠在qgraphicsscene中
- 保存对象并以任何特定顺序加载它们
- 返回对象时CTOR/DTOR的顺序
- C++和QML的对象顺序创建QT
- 对象顺序表C;Field by choice