158

グローバル変数と静的変数を持つモジュールがアプリケーションに動的にリンクされるとどうなるかを理解しようとしています。モジュールとは、ソリューション内の各プロジェクトを意味します (私は Visual Studio でよく作業します!)。これらのモジュールは、*.lib または *.dll、または *.exe 自体に組み込まれています。

アプリケーションのバイナリには、すべての個々の翻訳単位 (オブジェクト ファイル) のグローバル データと静的データがデータ セグメント (const の場合は読み取り専用データ セグメント) に含まれていることを理解しています。

  • このアプリケーションがモジュール A を読み込み時の動的リンクで使用するとどうなりますか? DLL にはグローバルとスタティックのセクションがあると思います。オペレーティング システムはそれらをロードしますか? もしそうなら、それらはどこにロードされますか?

  • また、アプリケーションがモジュール B を実行時の動的リンクで使用するとどうなるでしょうか?

  • アプリケーションに A と B の両方を使用する 2 つのモジュールがある場合、A と B のグローバルのコピーは以下に示すように作成されますか (それらが異なるプロセスである場合)。

  • DLL A と B はアプリケーション グローバルにアクセスできますか?

(理由も教えてください)

MSDNからの引用:

DLL ソース コード ファイルでグローバルとして宣言されている変数は、コンパイラとリンカーによってグローバル変数として扱われますが、特定の DLL をロードする各プロセスは、その DLL のグローバル変数の独自のコピーを取得します。静的変数のスコープは、静的変数が宣言されているブロックに限定されます。その結果、各プロセスには、デフォルトで DLL のグローバル変数と静的変数の独自のインスタンスがあります。

そしてここから:

モジュールを動的にリンクする場合、異なるライブラリに独自のグローバル インスタンスがあるかどうか、またはグローバルが共有されているかどうかが不明な場合があります。

ありがとう。

4

3 に答える 3

207

これは、Windows と Unix 系システムのかなり有名な違いです。

何があっても:

  • プロセスには独自のアドレス空間があります。つまり、プロセス間でメモリが共有されることはありません (プロセス間通信ライブラリまたは拡張機能を使用しない限り)。
  • One Definition Rule (ODR) は引き続き適用されます。つまり、リンク時に表示できるグローバル変数の定義は 1 つだけです (静的または動的リンク)。

したがって、ここでの重要な問題は実際には可視性です。

いずれの場合も、staticグローバル変数 (または関数) がモジュール (dll/so または実行可能ファイル) の外側から見えることはありません。C++ 標準では、これらが内部リンケージを持っている必要があります。つまり、それらが定義されている翻訳単位 (オブジェクト ファイルになる) の外では見えません。それで、それはその問題を解決します。

extern複雑になるのは、グローバル変数がある場合です。ここで、Windows と Unix ライクなシステムは完全に異なります。

Windows (.exe および .dll) の場合、externグローバル変数はエクスポートされたシンボルの一部ではありません。つまり、異なるモジュールは、他のモジュールで定義されたグローバル変数をまったく認識しません。これは、たとえば、externDLL で定義された変数を使用することになっている実行可能ファイルを作成しようとすると、リンカー エラーが発生することを意味します。これは許可されていないためです。オブジェクト ファイル (または静的ライブラリ) にその extern 変数の定義を提供し、それを実行可能ファイルと DLL の両方に静的にリンクする必要があります。その結果、2 つの異なるグローバル変数 (1 つは実行可能ファイルに属し、もう 1 つは DLL に属します) になります。 )。

Windows でグローバル変数を実際にエクスポートするには、関数のエクスポート/インポート構文に似た構文を使用する必要があります。

#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif

MY_DLL_EXPORT int my_global;

これを行うと、グローバル変数がエクスポートされたシンボルのリストに追加され、他のすべての関数と同様にリンクできます。

Unix ライクな環境 (Linux など) の場合、拡張子を持つ「共有オブジェクト」と呼ばれる動的ライブラリは、すべてのグローバル変数 (または関数)を.soエクスポートします。externこの場合、任意の場所から共有オブジェクト ファイルへのロード時リンクを実行すると、グローバル変数が共有されます。つまり、1 つにリンクされます。基本的に、Unix ライクなシステムは、静的ライブラリと動的ライブラリのリンクにほとんど違いがないように設計されています。繰り返しになりますが、ODR は全面的に適用されます。externグローバル変数はモジュール間で共有されます。つまり、ロードされたすべてのモジュールで 1 つの定義のみを持つ必要があります。

最後に、どちらの場合も、Windows または Unix ライクなシステムの場合、動的ライブラリの実行時リンクを実行できます。つまり、LoadLibrary()/ GetProcAddress()/FreeLibrary()またはdlopen()/ dlsym()/のいずれかを使用しますdlclose()。その場合、使用したい各シンボルへのポインターを手動で取得する必要があり、これには使用したいグローバル変数が含まれます。グローバル変数の場合、グローバル変数がエクスポートされたシンボル リストの一部である場合 (前の段落の規則により)、関数の場合と同じようにGetProcAddress()orを使用できます。dlsym()

そしてもちろん、必要な最後の注意として、グローバル変数は避けるべきです。そして、あなたが引用したテキスト(「不明確」であることについて)は、私が説明したばかりのプラットフォーム固有の違いを正確に指していると思います(動的ライブラリは実際にはC++標準によって定義されていません。これはプラットフォーム固有の領域です。信頼性/移植性がはるかに低くなります)。

于 2013-10-15T06:02:00.730 に答える
2

Mikael Persson が残した回答は、非常に徹底していますが、解決する必要があるグローバル変数に関して重大なエラー (または少なくとも誤解を招く) が含まれています。元の質問では、グローバル変数の個別のコピーがあるかどうか、またはグローバル変数がプロセス間で共有されているかどうかを尋ねました。

正しい答えは次のとおりです。プロセスごとに個別の (複数の) グローバル変数のコピーがあり、それらはプロセス間で共有されません。したがって、1 つの定義規則 (ODR) が適用されるという記述は、非常に誤解を招くものでもあります。各プロセスで使用される同じグローバルではないという意味では適用されません。したがって、実際にはプロセス間の「1 つの定義」ではありません。

また、グローバル変数はプロセスに「表示」されていませんが、プロセスに常に簡単に「アクセス」できます。これは、どの関数でもグローバル変数の値をプロセスに簡単に返すことができるためです。関数呼び出しを介してグローバル変数の値を設定できます。したがって、この答えも誤解を招くものです。

実際には、「はい」プロセスは、少なくともライブラリへの関数呼び出しを通じて、グローバルに完全に「アクセス」できます。ただし、繰り返しますが、各プロセスには独自のグローバルのコピーがあるため、別のプロセスが使用しているグローバルとは異なります。

したがって、グローバルの外部エクスポートに関する回答全体は、実際にはトピックから外れており、不要であり、元の質問とは関係ありません。グローバルは extern にアクセスする必要がないため、ライブラリへの関数呼び出しを介してグローバルに常に間接的にアクセスできます。

もちろん、プロセス間で共有される唯一の部分は実際の「コード」です。コードは物理メモリ (RAM) の 1 か所にのみロードされますが、もちろん、同じ物理メモリの場所が各プロセスの「ローカル」仮想メモリの場所にマップされます。

反対に、静的ライブラリには、実行可能ファイル (ELF、PE など) に既に組み込まれている各プロセスのコードのコピーがあり、もちろん、動的ライブラリと同様に、プロセスごとに個別のグローバルがあります。

于 2022-01-15T06:53:29.313 に答える