标准要求实施执行以下操作:
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/f2\n";
f1();
f2();
} else {
std::cout << "f2/f1\n";
f2();
f1();
}
return 0;
}
问题是:实施如何遵守?如何跟踪每个结构?
答案 0 :(得分:0)
GCC 4.8.1与-std=c++11
生成以下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
以相反的顺序)。
答案 1 :(得分:0)
stanard说什么并建议什么?
让我们先来看看exit()
定义
18.5 / 8:首先,销毁具有线程存储持续时间并与当前线程关联的对象。接下来,具有静态的对象 存储持续时间被破坏,并通过调用注册功能 atexit [脚注221:每次注册时都会调用一个函数。]
然后我们提醒一下:
18.5 / 5 :atexit()函数注册f指向的函数,在正常程序终止时不带参数调用。
然后查看 3.6.3 / 3 中终止的操作顺序,我们发现在终止时,用atexit()
注册的函数按其注册的相反顺序调用。这听起来不熟悉吗?而且还有更多!该标准保证了对析构函数的调用顺序以及对atexit
注册的函数的调用顺序也是相反的。
严格地说,标准并没有说静态析构函数是通过atexit函数来管理的,但它表明存在一个非常强大的链接。这就是 许多C ++实现使用atexit mechanim破坏静态 的原因。
如何针对特定实施进行演示?
在您的示例中很难将编译器生成的代码专门用于静态破坏,而不是终止序列中生成的其他典型代码。我建议你以下经验:
更改代码以在不同的标头中定义结构,并将成员函数的实现放在不同的文件中。然后在项目中添加一个简单的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()
的调用,该调用注册生成的函数,即此特定变量的“动态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
...
此基本编译单元中几乎没有其他代码。