web-dev-qa-db-ja.com

C ++:C ++グローバル静的コンストラクターはいつ(そしてどのように)呼び出されますか?

私はいくつかのC++コードに取り組んでいて、しばらくの間私を悩ませてきた質問に遭遇しました...グローバルな静的コンストラクターとデストラクタがあるELFターゲットのLinuxホストでGCCをコンパイルしていると仮定しますと呼ばれる?

Crtbegin.oに関数_initがあり、crtend.oに関数_finiがあると聞きました。これらはcrt0.oによって呼び出されますか?または、ダイナミックリンカは実際にロードされたバイナリでそれらの存在を検出して呼び出しますか?もしそうなら、いつそれは実際にそれらを呼び出しますか?

コードがロードされ、実行され、実行時にアンロードされるときに、舞台裏で何が起こっているのかを理解できるように、私は主に知りたいと思っています。

前もって感謝します!

更新:私は基本的に、コンストラクターが呼び出される一般的な時間を把握しようとしています。この情報に基づいてコードで仮定を立てたくはありません。プログラムがロードされたときに、低レベルで何が起こっているのかをよりよく理解するためです。これはOS固有のものであることは理解していますが、この質問では少し絞り込んでみました。

25
Matthew Iselin

非ローカル静的オブジェクトについて話すとき、多くの保証はありません。あなたがすでに知っているように(そしてそれはここでも言及されています)、それに依存するコードを書くべきではありません。静的初期化順序の大失敗...

静的オブジェクトは、静的初期化と動的初期化の2段階の初期化を経ます。前者が最初に発生し、ゼロ初期化または定数式による初期化を実行します。後者は、すべての静的初期化が完了した後に発生します。これは、たとえばコンストラクターが呼び出されるときです。

一般に、この初期化はmain()の前のある時点で発生します。ただし、多くの人が考えていることとは対照的に、それでもC++標準では保証されていません。実際に保証されているのは、初期化されるオブジェクトと同じ変換単位で定義された関数またはオブジェクトを使用する前に、初期化が行われることです。これはOS固有ではないことに注意してください。これはC++ルールです。標準からの引用は次のとおりです。

名前空間スコープのオブジェクトの動的初期化(8.5、9.4、12.1、12.6.1)が、mainの最初のステートメントの前に実行されるかどうかは、実装によって定義されます。 mainの最初のステートメントの後のある時点まで初期化が延期される場合、初期化されるオブジェクトと同じ変換単位で定義された関数またはオブジェクトが最初に使用される前に発生するものとします。
18

これはOS固有ではなく、コンパイラ固有です。

あなたは答えを与えました、初期化は___init_で行われます。

2番目の部分では、gccで、変数定義に____attribute____((init_priority(PRIORITY)))を付加して初期化の順序を保証できます。ここで、PRIORITYは相対値であり、小さい数値が最初に初期化されます。

11
Gunther Piez

これは、コンパイラとランタイムに大きく依存します。グローバルオブジェクトが構築される時間を想定することはお勧めできません。

これは、すでに構築されている別のオブジェクトに依存する静的オブジェクトがある場合に特に問題になります。

これは「 静的初期化順序fiasco "」と呼ばれます。コードに当てはまらない場合でも、そのトピックに関するC++ Lite FAQの記事は読む価値があります。

10
ebo

あなたが持っている被付与者:

  • グローバル名前空間内のすべての静的非ローカルオブジェクトは、main()の前に構築されます
  • 別の名前空間内のすべての静的非ローカルオブジェクトは、その名前空間内の関数/メソッドが使用される前に構築されます(したがって、コンパイラーがそれらを遅延評価する可能性があります[ただし、この動作は期待しないでください])。
  • 翻訳ユニット内のすべての静的非ローカルオブジェクトは、宣言の順序で作成されます。
  • 翻訳単位間の順序については何も定義されていません。
  • すべての静的な非ローカルオブジェクトは、作成の逆の順序で破棄されます。 (これには、静的関数変数(最初の使用時に遅延して作成される)が含まれます。

相互に依存関係のあるグローバルがある場合は、次の2つのオプションがあります。

  • それらを同じ翻訳単位に入れます。
  • それらを、最初の使用時に取得および構築された静的関数変数に変換します。

例1:グローバルAのコンストラクターはグローバルログを使用します

class AType
{    AType()  { log.report("A Constructed");}};

LogType    log;
AType      A;

// Or 
Class AType() 
{    AType()  { getLog().report("A Constructed");}};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define A anywhere;

グローバルBのデストラクタがグローバルログを使用する例

ここでは、オブジェクトログがオブジェクトBの前に破棄されないことを許可する必要があります。これは、ログがBの前に完全に構​​築される必要があることを意味します(破棄ルールの逆の順序が適用されるため)。ここでも同じ手法を使用できます。それらを同じ変換単位に配置するか、関数を使用してログを取得します。

class BType
{    ~BType()  { log.report("B Destroyed");}};

LogType    log;
BType      B;   // B constructed after log (so B will be destroyed first)

// Or 
Class BType() 
{    BType()    { getLog();}
     /*
      * If log is used in the destructor then it must not be destroyed before B
      * This means it must be constructed before B 
      * (reverse order destruction guarantees that it will then be destroyed after B)
      *
      * To achieve this just call the getLog() function in the constructor.
      * This means that 'log' will be fully constructed before this object.
      * This means it will be destroyed after and thus safe to use in the destructor.
      */
    ~BType()    { getLog().report("B Destroyed");}
};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define B anywhere;
6
Martin York

C++標準によれば、変換ユニットの関数またはオブジェクトが使用される前に呼び出されます。グローバル名前空間内のオブジェクトの場合、これはmain()が呼び出される前に初期化されることを意味することに注意してください。 (モートの詳細とこれに関する説明については、 ltcmelo's および Martin's の回答を参照してください。)

5
sbi