7

ここには「静的初期化順序の大失敗」に関するいくつかの良い質問と回答がありますが、クラッシュはしないがデータを失い、リークするため、特に醜い、さらに別の表現にぶつかったようです。

カスタム C++ ライブラリと、それに対してリンクするアプリケーションがあります。ライブラリには、クラスのすべてのインスタンスを登録する静的 STL コンテナーがあります。これらのインスタンスは、たまたまアプリケーションの静的変数です。

「大失敗」の結果 (私は信じています)、アプリケーションの初期化中にコンテナーがアプリケーション インスタンスでいっぱいになり、ライブラリが初期化され、コンテナーがリセットされ (おそらくメモリ リーク)、図書館。

これは、単純化されたコードで再現した方法です。

mylib.hpp:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class MyLibClass {
    static vector<string> registry;
    string myname;
  public:
    MyLibClass(string name);
};

mylib.cpp:

#include "mylib.hpp"

vector<string> MyLibClass::registry;

MyLibClass::MyLibClass(string name)
: myname(name)
{
    registry.push_back(name);
    for(unsigned i=0; i<registry.size(); i++)
        cout << " ["<< i <<"]=" << registry[i];
    cout << endl;
}

MyLibClass l1("mylib1");
MyLibClass l2("mylib2");
MyLibClass l3("mylib3");

myapp.cpp:

#include "mylib.hpp"

MyLibClass a1("app1");
MyLibClass a2("app2");
MyLibClass a3("app3");

int main() {
    cout << "main():" << endl;
    MyLibClass m("main");
}

オブジェクトを次のようにコンパイルします。

g++ -Wall -c myapp.cpp mylib.cpp
g++ myapp.o mylib.o -o myapp1
g++ mylib.o myapp.o -o myapp2

myapp1 を実行します。

$ ./myapp1
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main

myapp2 を実行します。

$ ./myapp2
 [0]=app1
 [0]=app1 [1]=app2
 [0]=app1 [1]=app2 [2]=app3
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main

静的ベクトルが再初期化されたのか、それとも初期化前に使用されたのかという質問があります。これは予期される動作ですか?

ライブラリを 'mylib.a' (ar rcs mylib.a mylib.o) として 'ar' すると、問題は発生しませんが、.a にリンクするための有効な順序が 1 つしかなく、それが原因である可能性があります。ライブラリは最後に、myapp1 はここにあります。

しかし、私たちの実際のアプリケーションでは、多くのオブジェクト ファイルといくつかの静的 (.a) ライブラリがいくつかの静的レジストリを共有する、より複雑なアプリケーションで問題が発生しており、これまでに解決できた唯一の方法は、'[10.15 ] 「静的な初期化順序の大失敗」を防ぐにはどうすればよいですか?' .

(正しくリンクしているかどうかを確認するために、多少複雑なビルド システムを調査中です)。

4

3 に答える 3

4

初期化順序の問題を回避する 1 つの方法は、静的変数をグローバル スコープからローカル スコープに移動することです。

クラス内に変数を持つ代わりにregistry、関数に入れます。

vector<string> & MyLibClass::GetRegistry()
{
    static vector<string> registry;
    return registry;
}

registry直接使用する場所では、 を呼び出しますGetRegistry

于 2011-03-24T17:13:51.803 に答える
3

カスタムコンストラクターをvector<string>指定すると、実際には一度だけ呼び出されることがわかりますが、最初に初期化されていないmyapp2ものを使用している場合はregistry、初期化され (内部にあるすべてのものを「削除」)、再度埋められます。セグメンテーション違反にならないのはただの運です:)

標準のどの部分がこの動作について何かを述べているかはわかりませんが、私見では、静的変数を相互に依存させる/絶対にしないでください。たとえば、レジ​​ストリに Meyers シングルトンを使用できます。

于 2011-03-24T17:06:16.350 に答える
0

2 つの既知のテクニックを使用しています。

(1) 「デバイス」パターンとしての「モジュール/ライブラリ/名前空間」

(2) 静的クラスを使用したカスタム型登録。

「Object Pascal」と「Plain C」で同様のことを行いました。いくつかのファイルがあり、各ファイルはモジュール/名前空間として機能し、typedef、クラス、関数を備えています。さらに、各「名前空間」には、デバイスの接続と切断をシミュレートする 2 つの特別なメソッド (同じ署名またはプロトタイプ) がありました。すでにそれらのメソッドを自動的に呼び出そうとしましたが、実行順序も間違っていました。

静的なシングルトン クラスは混乱する可能性があります。マクロまたはプリプロセッサ/コンパイラの使用を忘れて、初期化/ファイナライズ メソッドを自分で呼び出すことをお勧めします。

----
mylib.hpp
----

class MyLibClass {
  public:
    Register(string libraryName);
    UnRegister(string libraryName);
};

// don't execute the "custom type registration here"

-----
mynamespace01.cpp
-----
#include "mylib.hpp"

void mynamespace01_otherstuff() { ... }

// don't execute registration
void mynamespace01_start() { 
  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01");
}

void mynamespace01_finish()
{ 
  if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01");
}

-----
mynamespace02.cpp
-----
#include "mylib.hpp"

// check, "2" uses "1" !!!
#include "mynamespace01.hpp"

void mynamespace02_otherstuff() { ... }

// don't execute registration !!!
void mynamespace02_start() { 
  // check, "2" uses "1" !!!
  void mynamespace01_start();

  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02");

  void mynamespace02_start();  
}

void mynamespace02_finish(){ 
  void mynamespace02_finish();

  if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02");

  // check, "2" uses "1" !!!
  void mynamespace02_start();  
}

-----
myprogram.cpp
-----

#include "mynamespace01.hpp"
#include "mynamespace02.hpp"

void myprogram_otherstuff() { ... }

// don't execute registration !!!
void myprogram_start() { 
  // check, "2" uses "1" !!!
  mynamespace01_start();
  mynamespace02_start();

  if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram");
}
void myprogram_finish() {
  if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram");

  // check, "2" uses "1" !!!  
  mynamespace01_finish();
  mynamespace02_finish();  
}

void main () {
  // all registration goes here !!!:

  // "device" initializers order coded by hand:
  myprogram_start();

  // other code;

  // "device" finalizers order inverse coded by hand:  
  myprogram_finish();
}
-----

このコードがあなたのコードよりも複雑で冗長であることを確認してください。ただし、私の経験では、より安定しています。

また、「initializer」に「finalizer」を追加し、「Register」の識別子を置き換えます。

幸運を。

于 2011-03-24T17:26:32.670 に答える