簡體   English   中英

靜態對象銷毀順序如何計算?

[英]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/f2\n";
    f1();
    f2();
  } else {
    std::cout << "f2/f1\n";
    f2();
    f1();
  }

  return 0;
}

問題是:實施如何符合要求? 如何跟蹤每個構造?

具有-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指向的desctructor A%esi保持的地址a 退出時,執行與所述給定的參數調用給定的功能,(在這種情況下,它作為this以相反的順序在析構函數)。

錫納德說什么並暗示什么?

我們先來看一下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()的調用,該調用將注冊生成的函數,即此特定變量的“動態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
...

在這個基本的編譯單元中幾乎沒有其他代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM