如何计算静态对象销毁顺序?

时间:2014-11-15 21:37:40

标签: c++ static

标准要求实施执行以下操作:

  

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;
}

问题是:实施如何遵守?如何跟踪每个结构?

2 个答案:

答案 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
...

此基本编译单元中几乎没有其他代码。