今日は、実行可能モジュールのリンクが正しくない静的変数の陰湿な機能について説明します。 誰もが経験できる実際の練習からの問題を示します。
私はすべてをかなり詳細に噛んでいるので、「経験のある」人や目の肥えた人は、私が「サンドボックスをつついている」という感覚を感じるかもしれませんが、この記事は彼らだけのものではありません。
状況を想像してみましょう:静的ライブラリ(lib)に実装されたクラスがあります。 このライブラリは、実装モジュール(dll)によって静的にバインドされています。 さらに、この実行可能モジュール(exe)もこのdllを静的にバインドします。 さらに、Exeモジュールは静的ライブラリ(lib)を静的にリンクします。
このようなもの:
たとえば、次のロジックがあります。libには、何らかのツールが実装されています。 dllには、このツールに基づいたいくつかの機能があります。 エグゼは、この機能のテストを実装しています。 DLL自体は(lib'eにある)ツールクラスをエクスポートしないため、テストではlibの静的リンクが必要です。
インストルメンタルクラスに静的変数を含めます。 また、dllにはこのクラスを作成する関数があり、オブジェクトは値によって返されます。
拡張された概要は次のとおりです。
C ++コードは次のとおりです。
- lib
ListAndIter.h#pragma once #include <list> using namespace std; class ListAndIter { private: std::list<int>::iterator iter; static std::list<int> &getList(); public: void foo(); ListAndIter(); ListAndIter(ListAndIter& rhs); ~ListAndIter(); };
ListAndIter.cpp#include "ListAndIter.h" ListAndIter::ListAndIter() { getList().push_front(0); iter = getList().begin(); } ListAndIter::ListAndIter(ListAndIter& rhs) { this->iter = rhs.iter; rhs.iter = getList().end(); } std::list<int> & ListAndIter::getList() { static std::list<int> MyList; return MyList; } ListAndIter::~ListAndIter() { if (iter != getList().end()) getList().erase(iter); } void ListAndIter::foo() { }
- dll
GetStaticObj.h#pragma once #include "ListAndIter.h" #ifdef _DLL_EXPORTS #define _DLL_EXP __declspec(dllexport) #else #define _DLL_EXP __declspec(dllimport) #endif _DLL_EXP ListAndIter GetStaticObj();
GetStaticObj.cpp#include "GetStaticObj.h" ListAndIter GetStaticObj() { ListAndIter obj; obj.foo(); return obj; }
- exe
Main.cpp#include "GetStaticObj.h" int main() { ListAndIter obj = GetStaticObj(); obj.foo(); }
ListAndIterクラスのオブジェクトはコピーコンストラクターを介して返されるため、exeモジュール側でオブジェクトを受け取ると、静的変数へのすべての参照が無効になります。 手順では、次のようになります。
- * .exe:GetStaticObj()関数を呼び出します。
- Dll.dll:ListAndIterクラスの一時オブジェクトの作成。 ゼロがリストに入れられ、反復子イテレーターがそれを指します。 さらに、この時点で、exeモジュール側の静的変数はそれぞれ空であり、反復子は無効です。
- Dll.dll:クラスListAndIterのオブジェクトのコピーコンストラクターが呼び出されます。 イテレータが一時オブジェクトに対して無効になりました。 新しいオブジェクトの場合、イテレータはDLL.dllのリストを指しますが、オブジェクト自体はexeモジュールの側で作成されます。
- Dll.dll:ListAndIterクラスの一時オブジェクトを破壊しました。 イテレータは無効であるため、アクションは発生しません。
- * .exe:objオブジェクトのデストラクタが呼び出されます。 イテレータをgetList()。End()と比較しようとすると、Windowsエラーがポップアップします:「イテレータは互換性がありません。」 つまり、「その他のリスト」のイテレーターです。
静的ライブラリへのexeモジュールの依存関係を削除することにより、この状況を修正してみましょう。 次に、静的ライブラリのすべての機能をdll経由でエクスポートする必要があります(以下のコードを参照)。
コードの変更:
- 新しいshared.hヘッダーファイルを作成しました。 エクスポートマクロについて説明します。 ファイルをlibに配置しました。
shared.h#pragma once #ifdef _DLL_EXPORTS #define _DLL_EXP __declspec(dllexport) #else #define _DLL_EXP __declspec(dllimport) #endif
- ListAndIter.hにエクスポートディレクティブを追加しました。
ListAndIter.h#pragma once #include <list> #include "shared.h" using namespace std; class ListAndIter { private: std::list<int>::iterator iter; _DLL_EXP static std::list<int> &getList(); public: _DLL_EXP void foo(); _DLL_EXP ListAndIter(); _DLL_EXP ListAndIter(ListAndIter& rhs); _DLL_EXP ~ListAndIter(); };
- それに応じて、dllでエクスポートマクロ宣言を削除しました。
GetStaticObj.h#pragma once #include "ListAndIter.h" #include "shared.h" _DLL_EXP ListAndIter GetStaticObj();
ListAndIterクラスがボイラープレートになった場合にどうなるかを考えてみましょう。
テンプレートとそのようなクラスのすべてのオブジェクトの完全な特殊化ごとに、静的変数が必要です。
まず、ヘッダーファイルにテンプレートクラスの実装を配置する必要があります。 テンプレートはコンパイル段階で公開されます。
静的変数がクラスのメンバーである場合、プロジェクトを正常にアセンブルするために、使用されるすべてのモジュールでこれらの変数を明示的に初期化する必要があります。 この場合、2つの静的変数を明示的に作成し、最初の例に戻ります。
それ以外の場合、静的変数がクラスのメンバーではなく、静的メソッドを介して作成される場合、この場合も静的変数は作成されますが、すでに暗黙的に作成されています。 エラーが再び繰り返されます。
この状況を解決するには、この機能を配置するための中間ライブラリを作成する必要があります。 つまり、dllの代わりにlibを実行します。 その後、再び1つの静的変数が残ります。
結論 :静的ライブラリで静的変数を使用する場合、実行可能モジュールが互いに静的にリンクしないようにする必要があります。
依存関係を単純化しても問題が解決しない場合があります。 たとえば、クラスは静的ライブラリに実装され、特定の静的インスタンスカウンターがあります。 この静的ライブラリは2つの異なるdllにリンクするため、2つの異なるカウンタが作成されます。 この場合、静的ライブラリを動的ライブラリ(dll)に変えることで問題は解決します。 したがって、他の2つのdllは新しいdllを動的にリンクします。 その場合、静的変数は1つのdllにのみ存在します(カウンターを持つクラスが実装されているdllに存在します)。
すべてのコードはgithubから取得できます。
PS私は多くのことを書きましたが、完璧ではないかもしれません...私はアドバイスやコメントを喜んでいます。