标准是怎么说和建议的?
我们先来看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,有初始化代码(我添加的 cmets):
??__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
...
而且这个基本编译单元中几乎没有其他代码。