リンカー初心者ガイド

David Drysdaleリンカーの初心者向けガイドhttp://www.lurklurk.org/linkers/linkers.html )。



この記事の目的は、CおよびC ++プログラマーがリンカの本質を理解できるようにすることです。 過去数年にわたって、私はこれを多くの同僚に説明し、最終的にこの資料を紙に移し、よりアクセスしやすくすることにしました(したがって、再度説明する必要はありませんでした)。 [2009年3月の更新:Windowsのレイアウト機能に関する追加情報と、より詳細な1定義ルールが追加されました。



私が助けを求められた典型的な例は、次のレイアウトエラーです。

g++ -o test1 test1a.o test1b.o test1a.o(.text+0x18): In function `main': : undefined reference to `findmax(int, int)' collect2: ld returned 1 exit status
      
      





あなたの反応が「おそらくextern“ C”を忘れた」なら、この記事で与えられているすべてを知っているでしょう。



内容





定義:Cファイルには何が含まれていますか?



この章では、Cファイルのさまざまなコンポーネントについて簡単に説明します。 以下リストのすべてが理にかなっている場合は、この章をスキップして次の章に進んでください。



まず、宣言と定義の違いを理解する必要があります。 定義は 、名前を実装に関連付けます。実装は、コードまたはデータのいずれかです。



この宣言は、関数または変数の定義(特定の名前を持つ)がプログラム内の別の場所、おそらく別のCファイルに存在することをコンパイラーに伝えます。 (定義はアナウンスでもあることに注意してください-実際、それはプログラムの他の場所が現在のものと一致するアナウンスです)。



変数には2種類の定義があります。



さらに、「使用可能」という用語は、「定義時に変数に関連付けられた名前で参照できる」と理解する必要があります。



初めてでは明らかではない特別なケースがいくつかあります。



関数を静的として定義することにより、この関数に名前でアクセスできる場所の数が単純に減ることに注意してください。



グローバル変数とローカル変数では、初期化された変数と初期化されていない変数を区別できます。 メモリ内の変数用に予約されたスペースが特定の値で満たされるかどうか。



最後に、 malloc



またはnew



を使用して動的に割り当てられた情報をメモリに保存できます。 この場合、割り当てられたメモリに名前でアクセスすることはできないため、ポインタ-名前のないメモリ領域のアドレスを含む名前付き変数を使用する必要があります。 このメモリ領域は、 free



またはdelete



を使用して解放することもできます。 この場合、「動的割り当て」を扱っています。



要約すると:

コード データ
グローバル ローカル ダイナミック
イニシア

リシロ

バスルーム
非開始

リシロ

バスルーム
イニシア

リシロ

バスルーム
非開始

リシロ

バスルーム
発表

怠laz
int fn(int x);



extern int x;



extern int x;



N / a N / a N / a
定義する

怠laz
int fn(int x) { ... }



int x = 1;





(スコープ

-ファイル)
int x;





(スコープ-ファイル)
int x = 1;





(スコープ-機能)
int x;





(スコープ-機能)
int* p = malloc(sizeof(int));





おそらく最も簡単な学習方法は、サンプルプログラムを見るだけです。

 /*     */ int x_global_uninit; /*     */ int x_global_init = 1; /*    ,   *         C  */ static int y_global_uninit; /*    ,   *         C  */ static int y_global_init = 2; /*   ,   - *     */ extern int z_global; /*  ,   -   *  (    "extern",   * ) */ int fn_a(int x, int y); /*  .     static,   *        C . */ static int fn_b(int x) { return x+1; } /*  . */ /*     . */ int fn_c(int x_local) { /*     */ int y_local_uninit; /*     */ int y_local_init = 3; /* ,       , *      */ x_global_uninit = fn_a(x_local, x_global_init); y_local_uninit = fn_a(x_local, y_local_init); y_local_uninit += fn_b(z_global); return (x_global_uninit + y_local_uninit); }
      
      







Cコンパイラが行うこと



Cコンパイラの仕事は、テキスト(通常)を人間が読める形式に変換して、コンピューターが理解できるものにすることです。 出力で、コンパイラはオブジェクトファイルを生成します 。 UNIXプラットフォームでは、これらのファイルには通常、接尾辞.oが付いています。 Windowsでは、接尾辞.obj。 オブジェクトファイルの内容は、本質的に2つのものです。



この場合、コードとデータには名前が関連付けられます。定義に関連付けられている関数または変数の名前です。



オブジェクトコードは、プログラマーによって書かれたC命令に対応する(適切に構成された)機械命令のシーケンスです。これらはすべてif



while



およびgoto



さえもです。 これらの呪文は特定の種類の情報を操作する必要があり、情報はどこかにある必要があります-このためには変数が必要です。 コードは他のコード(特に、プログラム内の他のC関数)を参照する場合もあります。



コードが変数または関数を参照する場合はいつでも、コンパイラ 、以前にその変数または関数の宣言を見た場合にのみこれを許可します。 アナウンスは、定義がプログラムの他の場所に存在するという約束です。



リンカの仕事はこれらの約束を検証することです。 しかし、コンパイラはオブジェクトファイルを生成するときにこれらのすべての約束をどうしますか?



基本的に、コンパイラは空のスペースを残します。 空の場所(リンク)には名前がありますが、この名前に対応する値はまだわかっていません。



これにより、 上記プログラムに対応するオブジェクトファイルを次のように表示できます。

オブジェクトファイル図



オブジェクトファイルの解析



これまでのところ、すべてを高いレベルで検討してきました。 ただし、これが実際にどのように機能するかを見ると便利です。 主なツールはnm



コマンドで、UNIXプラットフォーム上のオブジェクトファイルのシンボルに関する情報を提供します。 Windowsの場合、 /symbols



オプションを指定したdumpbin



コマンドはほぼ同等です。 nm.exe



を含むWindows用に移植された GNU binutilsツールもあります。



上記の例から取得したオブジェクトファイルに対してnm



生成するものを見てみましょう。

 Symbols from c_parts.o: Name Value Class Type Size Line Section fn_a | | U | NOTYPE| | |*UND* z_global | | U | NOTYPE| | |*UND* fn_b |00000000| t | FUNC|00000009| |.text x_global_init |00000000| D | OBJECT|00000004| |.data y_global_uninit |00000000| b | OBJECT|00000004| |.bss x_global_uninit |00000004| C | OBJECT|00000004| |*COM* y_global_init |00000004| d | OBJECT|00000004| |.data fn_c |00000009| T | FUNC|00000055| |.text
      
      





結果はプラットフォームによって多少異なるように見える場合があります(関連情報についてはman



問い合わせてください)が、重要な情報は各文字のクラスとそのサイズ(存在する場合)です。 クラスにはさまざまな意味があります。



ソースCコードの一部ではない文字も表示できます。 通常はコンパイラの内部メカニズムの一部であるため、これに注意を集中することはありません。したがって、プログラムは後でコンパイルできます。



リンカの機能:パート1



前に、関数または変数の宣言はコンパイラーへの約束であり、プログラムの他のどこかにその関数または変数の定義があり、リンカーの仕事はその約束をすることであると述べました。 オブジェクトファイルの図を見ると、このプロセスを「空のスペースを埋める」と説明できます



上記の例に加えて、別のCファイルを考慮した例を使用してこれを説明します。

 /*    */ int z_global = 11; /*      y_global_init,    static */ static int y_global_init = 2; /*     */ extern int x_global_init; int fn_a(int x, int y) { return(x+y); } int main(int argc, char *argv) { const char *message = "Hello, world"; return fn_a(11,12); }
      
      





オブジェクトファイルの概略図



両方の図に基づいて、すべてのポイントを接続できることがわかります(接続されていない場合、リンカーはエラーメッセージを表示します)。 すべてのものには独自の場所があり、すべての場所には独自の場所があります。 また、リンカーは、ここに示すようにすべての空のスペースを埋めることができます(UNIXシステムでは、通常、リンクプロセスはld



コマンドによって呼び出されます)。



オブジェクトファイルの概略図



オブジェクトファイルと同様に、 nm



を使用して最終的な実行可能ファイルを調べることができます。

 Symbols from sample1.exe: Name Value Class Type Size Line Section _Jv_RegisterClasses | | w | NOTYPE| | |*UND* __gmon_start__ | | w | NOTYPE| | |*UND* __libc_start_main@@GLIBC_2.0 | U | FUNC|000001ad| |*UND* _init |08048254| T | FUNC| | |.init _start |080482c0| T | FUNC| | |.text __do_global_dtors_aux|080482f0| t | FUNC| | |.text frame_dummy |08048320| t | FUNC| | |.text fn_b |08048348| t | FUNC|00000009| |.text fn_c |08048351| T | FUNC|00000055| |.text fn_a |080483a8| T | FUNC|0000000b| |.text main |080483b3| T | FUNC|0000002c| |.text __libc_csu_fini |080483e0| T | FUNC|00000005| |.text __libc_csu_init |080483f0| T | FUNC|00000055| |.text __do_global_ctors_aux|08048450| t | FUNC| | |.text _fini |08048478| T | FUNC| | |.fini _fp_hw |08048494| R | OBJECT|00000004| |.rodata _IO_stdin_used |08048498| R | OBJECT|00000004| |.rodata __FRAME_END__ |080484ac| r | OBJECT| | |.eh_frame __CTOR_LIST__ |080494b0| d | OBJECT| | |.ctors __init_array_end |080494b0| d | NOTYPE| | |.ctors __init_array_start |080494b0| d | NOTYPE| | |.ctors __CTOR_END__ |080494b4| d | OBJECT| | |.ctors __DTOR_LIST__ |080494b8| d | OBJECT| | |.dtors __DTOR_END__ |080494bc| d | OBJECT| | |.dtors __JCR_END__ |080494c0| d | OBJECT| | |.jcr __JCR_LIST__ |080494c0| d | OBJECT| | |.jcr _DYNAMIC |080494c4| d | OBJECT| | |.dynamic _GLOBAL_OFFSET_TABLE_|08049598| d | OBJECT| | |.got.plt __data_start |080495ac| D | NOTYPE| | |.data data_start |080495ac| W | NOTYPE| | |.data __dso_handle |080495b0| D | OBJECT| | |.data p.5826 |080495b4| d | OBJECT| | |.data x_global_init |080495b8| D | OBJECT|00000004| |.data y_global_init |080495bc| d | OBJECT|00000004| |.data z_global |080495c0| D | OBJECT|00000004| |.data y_global_init |080495c4| d | OBJECT|00000004| |.data __bss_start |080495c8| A | NOTYPE| | |*ABS* _edata |080495c8| A | NOTYPE| | |*ABS* completed.5828 |080495c8| b | OBJECT|00000001| |.bss y_global_uninit |080495cc| b | OBJECT|00000004| |.bss x_global_uninit |080495d0| B | OBJECT|00000004| |.bss _end |080495d4| A | NOTYPE| | |*ABS*
      
      





両方のオブジェクトファイルの文字が含まれており、未定義の参照はすべて表示されなくなりました。 シンボルは、同様のタイプが一緒になるように並べ替えられます。 また、OSが実行可能ファイルなどを処理するのに役立ついくつかのアドオンもあります。



結論が複雑になるほど多くの複雑な詳細がありますが、アンダースコアで始まるものをすべて捨てると、はるかに簡単になります。



重複する文字



前の章では、リンクが見つかったシンボルの定義を見つけることができない場合、リンカはエラーメッセージを出すと述べました。 また、レイアウト中にキャラクターの2つの定義が見つかった場合はどうなりますか?



C ++では、ソリューションは簡単です。 この言語には1つの定義のルールとして知られる制限があり、レイアウト中に遭遇する各文字に対して1つだけの定義が必要であり、それ以上ではありません。 (C ++標準の対応する章は3.2であり、いくつかの例外についても言及しています。これについては後で説明します 。)



Cの場合、事態はそれほど明白ではありません。 関数と初期化されたグローバル変数の定義は正確に1つでなければなりませんが、初期化されていない変数の定義は予備的な定義として解釈できます。 したがって、C言語は、さまざまなソースファイルに同じオブジェクトの予備的な定義を含めることを許可します(少なくとも禁止しません)。



ただし、リンカはCおよびC ++以外の言語を処理できる必要があります。CおよびC ++では、1つの定義の規則が必ずしも尊重されません。 たとえば、Fortranがそれを参照するすべてのファイルのすべてのグローバル変数のコピーを保持するのは正常です。 次に、リンカーは、1つのコピー(サイズが異なる場合は最大の代表)を選択して重複を削除し、残りを破棄する必要があります。 このモデルは、Fortran COMMON(共通)キーワードにより、レイアウトの「一般モデル」と呼ばれることもあります。



結果として、少なくとも初期化されていないグローバル変数の重複文字である場合、UNIXリンカが重複文字の存在を誓わないことは非常に一般的です(このレイアウトモデルは「疎結合のモデル」と呼ばれることもあります[ translに注意してください。 defモデル。より良い提案を歓迎します])。 これが気になる場合(おそらく気になるはずです)、リンカのドキュメントを参照して、その動作を緩和する-work ---



オプションを見つけてください。 たとえば、GNUツールチェーンでは、 -fno-common



コンパイラオプションを使用すると、共通(COMMON)ブロックを生成する代わりに、BBSセグメントに初期化されていない変数を強制-fno-common



に配置できます。



オペレーティングシステムは何をしますか?



リンカが実行可能ファイルを生成し、シンボルへの各リンクに適切な定義を割り当てたので、実行のためにプログラムを実行するときにオペレーティングシステムが何を行うかを簡単に理解できます。



もちろんプログラムを実行するには、マシンコードの実行が必要です。 OSは明らかに、実行可能ファイルのマシンコードをハードディスクからオペレーティングメモリに転送する必要があり、そこからCPUがそれを取得できます。 これらの部分は、コードセグメント(コードセグメントまたはテキストセグメント)と呼ばれます。



データのないコードはそれ自体では役に立ちません。 したがって、すべてのグローバル変数にはコンピューターのメモリ内の場所も必要です。 ただし、初期化されたグローバル変数と初期化されていないグローバル変数には違いがあります。 初期化された変数には特定の開始値があり、これもオブジェクトおよび実行可能ファイルに保存する必要があります。 プログラムが開始時に開始されると、OSはこれらの値をプログラムの仮想空間のデータセグメントにコピーします。



初期化されていない変数の場合、OSはすべての変数が初期値として0を持っていると想定する場合があります。 値をコピーする必要はありません。 ゼロに初期化されるメモリの一部は、bssセグメントと呼ばれます。



つまり、グローバル変数用のスペースは、ディスクに保存されている実行可能ファイルに割り当てることができます。 初期化された変数の場合、その初期値を保存する必要がありますが、初期化されていない変数の場合、サイズを保存するだけです。







お気づきかもしれませんが、これまでオブジェクトファイルとリンカに関するすべての議論で、グローバル変数についてのみ話してきました。 前述のローカル変数と動的に占有されるメモリについては言及しませんでし



このデータは、プログラムの実行中に有効期間が開始および終了するため、リンカーの介入を必要としません。これは、リンカーがすでにジョブを実行したよりもずっと後のことです。 ただし、説明を完全にするために、次のことを簡単に示します。



図を完成させるには、実行中のプロセスのメモリ空間がどのようなものかを追加する価値があります。 ヒープとスタックは動的にサイズを変更できるため、スタックが一方向に成長し、ヒープが反対方向に成長することは非常に一般的です。 したがって、スタックとヒープが中間のどこかで出会った場合にのみ、プログラムは空きメモリ不足のエラーを出します(この場合、プログラムのメモリ空間は本当にいっぱいになります)。







リンカが行うこと; パート2



リンカが行うことの基本を説明したので、より複雑な部分の説明を、リンカに追加されたときとほぼ同じ順序で説明します。



リンカの機能に影響する主な観察事項は次のとおりです:多数の異なるプログラムが同じこと(画面への出力、ハードディスクからのファイルの読み取りなど)を行う場合、このコードを特定の場所に分離して他の人に与えることは明らかに理にかなっていますそれを使用するプログラム。



考えられる解決策の1つは、同じオブジェクトファイルを使用することですが、オブジェクトファイルのコレクション全体を...簡単にアクセスできる1つの場所に保存する方がはるかに便利です。



技術的な余談:この章では、リンカーの重要なプロパティである再配置を完全に省略しています。 異なるプログラムには異なるサイズがあります。 共有ライブラリがさまざまなプログラムのアドレス空間にマップされている場合、異なるアドレスになります。 , . , (« +1020 ») (« 0x102218BF»), , . — relocation . , , C/C++ — .





. , (share), ; .



UNIX ar , , , *.a. «lib» "-l" (.. "-lfred" «libfred.a»).

( , ranlib



, , . ar



.)



Windows .LIB



LIB, , «import library», , DLL — Windows DLL



リンカは、オブジェクトファイルのコレクションを反復処理してそれらを結合するときに、まだ実装できない文字のリストを保持します。明示的に指定されたすべてのオブジェクトファイルが処理されるとすぐに、リンカーはリスト内に残っている文字を検索するための新しい場所、つまりライブラリになります。ライブラリオブジェクトの1つで未実現シンボルが定義されている場合、オブジェクトがユーザーによってオブジェクトファイルのリストに追加されたかのようにオブジェクトが追加され、レイアウトが続行されます。



ライブラリから追加されるものの粒度に注意してください:特定の文字を定義する必要がある場合は、オブジェクト全体 , , . , , — , .



; , , . , , , , , .



, ; , ao, bo, -lx



および-ly







ファイル ao



bo



libx.a



liby.a



対象 ao



bo



x1.o



x2.o



x3.o



y1.o



y2.o



y3.o



Oprede-

Lenia
a1, a2, a3



b1, b2



x11, x12, x13



x21, x22, x23



x31, x32



y11, y12



y21, y22



y31, y32



解決できない

shonnyeの

リンク
b2, x12



a3, y22



x23, y12



y11



y21



x31





ao



bo



, b2



a3



, x12



y22



. libx.a



, x1.o



, x12



; , x23



y12



( y22, x23, y12



).



libx.a



, x23



, x2.o



libx.a



. y11



( y22, y12, y11



). libx.a



そのため、リンカはに使用されliby.a



ます。



ここでも同じことが起こり、リンカーにはとが含まy1.o



y2.o



ます。最初のオブジェクトにへのリンクが追加されますがy21



y2.o



まだ含まれるため、このリンクは単純に解決されます。このプロセスの結果、未定義の参照はすべて解決され、一部の(すべてではない)ライブラリオブジェクトが最終的な実行可能ファイルに含まれます。



たとえば、へbo



のリンクがあった場合は、状況が多少変わることに注意してくださいy32



。その場合、レイアウトlibx.a



も発生しますが、処理にliby.a



は包含が必要になりますy3.o



。このオブジェクトを含めることにより、追加しますx31



未解決の文字のリストに追加すると、このリンクは未解決のままになります。この段階では、リンカはすでに処理libx.a



完了しているため、このシンボルの定義は見つかりません(c x3.o



)。



(ちなみに、この例はライブラリlibx.a



ライブラリの間に周期的な依存関係がありliby.a



ます;これは通常Windowsで特に悪いです



動的共有ライブラリ



C ( libc



) — . , printf



, fopen



, .



, . - printf



, , .



, ( .so



.dll



Windows .dylib



Mac OS X)。このタイプのライブラリの場合、リンカーは必ずしもすべてのポイントを接続するとは限りません。代わりに、リンカーは「IOU」タイプのクーポンを発行し(私はあなたを借りる=私はあなたを借りる)、プログラムが開始するまでこのクーポンの換金を遅らせます。



これらすべては、特定の文字の定義が共有ライブラリにあることをリンカーが発見した場合、最終的な実行可能ファイルにこの定義を含めないという事実に要約されます。代わりに、リンカは、シンボルの名前と、そのシンボルが表示されるはずのライブラリを書き込みます。



プログラムが実行のために呼び出されると、OSは、ビルドプロセスの残りの部分がプログラムの開始前に時間通りに完了することを保証します。関数が呼び出される前main



に、リンカーの小さなバージョン(多くの場合、ld.so



) — .



, printf



. printf



, libc.so



printf



.



, . ( printf



libc.so



), . , , .



, ( , ar



), . , nm



— : , , liby.so



x31



. : y32



bc



, y3.o



x3.o



.



, — ldd



; Unix , ( ), , . , . ( , LD_LIBRARY_PATH



.)

 /usr/bin:ldd xeyes linux-gate.so.1 => (0xb7efa000) libXext.so.6 => /usr/lib/libXext.so.6 (0xb7edb000) libXmu.so.6 => /usr/lib/libXmu.so.6 (0xb7ec6000) libXt.so.6 => /usr/lib/libXt.so.6 (0xb7e77000) libX11.so.6 => /usr/lib/libX11.so.6 (0xb7d93000) libSM.so.6 => /usr/lib/libSM.so.6 (0xb7d8b000) libICE.so.6 => /usr/lib/libICE.so.6 (0xb7d74000) libm.so.6 => /lib/libm.so.6 (0xb7d4e000) libc.so.6 => /lib/libc.so.6 (0xb7c05000) libXau.so.6 => /usr/lib/libXau.so.6 (0xb7c01000) libxcb-xlib.so.0 => /usr/lib/libxcb-xlib.so.0 (0xb7bff000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb7be8000) libdl.so.2 => /lib/libdl.so.2 (0xb7be4000) /lib/ld-linux.so.2 (0xb7efb000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb7bdf000)
      
      





, , , , . , , ( bss — , , , strtok



). , , . , ao



co



, bo



co



, .



Windows DLL



共有ライブラリの一般的な原則はUnixとWindowsの両方のプラットフォームでほぼ同じであるという事実にもかかわらず、初心者が理解できるいくつかの詳細がまだあります。



エクスポートされたシンボル

最大の違いは、Windowsライブラリでは文字が自動的にエクスポートされないことです。Unixでは、共有ライブラリに対して認証されたすべてのオブジェクトファイルのすべての文字が、このライブラリのユーザーに表示されます。Windowsでは、プログラマは明示的に一部の文字を表示する必要があります。それらをエクスポートします。



シンボルとWindows DLLをエクスポートするには3つの方法があります(これら3つの方法はすべて同じライブラリに混在させることができます)。



C ++がこのmishmashに接続されるとすぐに、これらのオプションの最初のオプションが最も簡単になります。この場合、コンパイラは名前装飾を引き受けるためです。



.LIB



およびその他のライブラリ関連ファイル

Windowsライブラリに関連する2番目の困難に直面します。エクスポートされたシンボルに関する情報は、リンカーが他のシンボルと関連付ける必要がありますが、それ自体には含まれていませんDLL



。代わりに、この情報は対応する.LIB



ファイルに含まれています。



.LIB



に関連付けられているファイルはDLL



、どの(エクスポートされた)文字がDLL



その場所とともに含まているかを説明します。を使用するバイナリは、文字を正しく関連付けるためにファイルにDLL



アクセスする必要があります.LIB







物事をさらに混乱させるために、拡張機能は.LIB



静的ライブラリにも使用されます。



実際、Windowsライブラリに何らかの形で関連する可能性のあるさまざまなファイルがいくつかあります。とともに.LIB



ファイル、および(オプションの).DEF



ファイルを使用すると、Windowsライブラリに関連付けられている次のすべてのファイルを表示できます。



これは、これらすべての追加ファイルに含まれるほぼすべての情報が単にライブラリ自体に追加されるUnixとの大きな違いです。



インポートされたキャラクター

DLLは、エクスポートされた文字を明示的に宣言するための要件に加えて、ライブラリコードを使用してインポートする文字を明示的に宣言するバイナリも許可します。これは必須ではありませんが、16ビットウィンドウの履歴プロパティによって引き起こされる速度の最適化を提供します



これを行うには、次のようにソースコードで__declspec(dllimport)として文字を宣言します。

 __declspec(dllimport) int function_from_some_dll(int x, double y); __declspec(dllimport) extern int global_var_from_some_dll;
      
      





C. : DLL, / , , DLL, .



— .

 #ifdef EXPORTING_XYZ_DLL_SYMS #define XYZ_LINKAGE __declspec(dllexport) #else #define XYZ_LINKAGE __declspec(dllimport) #endif XYZ_LINKAGE int xyz_exported_function(int x); XYZ_LINKAGE extern int xyz_exported_variable;
      
      





DLL, , EXPORTING_XYZ_DLL_SYMS



( #define



) . , .



, DLL, , Windows , . Unix , , .. , , , , . Windows .



— . , , — , , ,



, , . X.DLL



Y.DLL



, Y.DLL



X.DLL



, : , .



Windows .



しかし、周期的な依存関係を回避するような方法でライブラリを再編成することは確かに優れています...



画像を補完するC ++



C++ , C, . — C++ C, . , , .





C++ , , , ( ):

 int max(int x, int y) { if (x>y) return x; else return y; } float max(float x, float y) { if (x>y) return x; else return y; } double max(double x, double y) { if (x>y) return x; else return y; }
      
      





: - max



, ?



(name mangling), (to mangle = , , .. ) , . . .



( ), , , , (, nm



— !):

 Symbols from fn_overload.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* _Z3maxii |00000000| T | FUNC|00000021| |.text _Z3maxff |00000022| T | FUNC|00000029| |.text _Z3maxdd |0000004c| T | FUNC|00000041| |.text
      
      





max



, , , «max» — «i» int



, «f» float



«d» double



( , , , !).



, , , . (, c++filt



) ( --demangle



GNU nm), - :

 Symbols from fn_overload.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* max(int, int) |00000000| T | FUNC|00000021| |.text max(float, float) |00000022| T | FUNC|00000029| |.text max(double, double) |0000004c| T | FUNC|00000041| |.text
      
      





, , C C++. , C++ , ; , C , , . , C++ extern "C"



. C++ , — C++ , C, C , C++.



, , , - extern "C"



C C++ .

 g++ -o test1 test1a.o test1b.o test1a.o(.text+0x18): In function `main': : undefined reference to `findmax(int, int)' collect2: ld returned 1 exit status
      
      





素晴らしいヒントは、エラーメッセージに関数の署名が含まれていることです。これは、単に見つからなかったというメッセージではありfindmax



ません。つまり、C ++コードはのようなものを探しますが、"_Z7findmaxii"



のみを見つけ"findmax"



ます。したがって、レイアウトエラーが発生します。



ところで、extern "C"



クラスのメンバー関数では宣言が無視されることに注意してください(C ++標準の7.5.4)



静的オブジェクトの初期化



C++, , — . — , . , , .



, . C — : , ---.



C ++では、初期化プロセスは単に固定値をコピーするよりもはるかに複雑になる可能性があります。プログラム自体が実際に実行を開始する前に、クラス階層全体のさまざまなコンストラクターのすべてのコードを実行する必要があります。



これを処理するために、コンパイラは各C ++ファイルのオブジェクトファイルに少し余分な情報を配置します。つまり、これは特定のファイルに対して呼び出す必要があるコンストラクタのリストです。リンク時に、リンカーはこれらのリストをすべて1つの大きなリストに結合し、リスト全体を通過するコードを配置して、すべてのグローバルオブジェクトのコンストラクターを呼び出します。順序に



注意してください , — , . (. « C++» — 47 , 4 )



, nm



. C++ :

 class Fred { private: int x; int y; public: Fred() : x(1), y(2) {} Fred(int z): x(z), y(3) {} }; Fred theFred; Fred theOtherFred(55);
      
      





( ) nm



:

 Symbols from global_obj.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* __static_initialization_and_destruction_0(int, int) |00000000| t | FUNC|00000039| |.text Fred::Fred(int) |00000000| W | FUNC|00000017| |.text._ZN4FredC1Ei Fred::Fred() |00000000| W | FUNC|00000018| |.text._ZN4FredC1Ev theFred |00000000| B | OBJECT|00000008| |.bss theOtherFred |00000008| B | OBJECT|00000008| |.bss global constructors keyed to theFred |0000003a| t | FUNC|0000001a| |.text
      
      





, , W ( «» («weak» symbol)) ".gnu.linkonce.t. stuff ". , «Name» , — .



パターン



先ほど、関数の3つの異なる実装のを示しましmax



。それぞれがさまざまな型の引数を取りました。ただし、関数の本体コードは3つのケースすべてで同一であることがわかります。そして、同じコードを複製することは、悪いプログラミングトーンであることを知っています。



C ++ではテンプレートの概念が導入されているため、すべてのケースで以下のコードを一度に使用できます。max_template.h



関数コードのコピーを1つだけ持つヘッダーファイル作成できますmax





 template <class T> T max(T x, T y) { if (x>y) return x; else return y; }
      
      





このファイルをソースファイルに含めて、テンプレート関数を試してください。

 #include "max_template.h" int main() { int a=1; int b=2; int c; c = max(a,b); //   ,    max<int>(int,int) double x = 1.1; float y = 2.2; double z; z = max<double>(x,y); //    ,   max<double>(double,double) return 0; }
      
      





C++ max<int>(int,int)



max<double>(double,double)



. , - . , , max<float>(float,float)



max<MyFloatingPointClass>(MyFloatingPointClass,MyFloatingPointClass)



.



. , , , ( , ).



これはどのように行われますか?通常、アクションの2つのパスがあります。反復する権限を間引くか、インスタンスをビルドフェーズに延期するかのいずれかです(通常、これらのアプローチをSunのスマートパスおよびパスと呼びます)。



インスタンスを繰り返す間引き方法は、各オブジェクトファイルに、検出されたすべてのパターンのコードが含まれていることを意味します。たとえば、上記のファイルの場合、オブジェクトファイルの内容は次のようになります。

 Symbols from max_template.o: Name Value Class Type Size Line Section __gxx_personality_v0 | | U | NOTYPE| | |*UND* double max<double>(double, double) |00000000| W | FUNC|00000041| |.text _Z3maxIdET_S0_S0_ int max<int>(int, int) |00000000| W | FUNC|00000021| |.text._Z3maxIiET_S0_S0_ main |00000000| T | FUNC|00000073| |.text
      
      





max<int>(int,int)



max<double>(double,double)



.



, , ( , ). — .



( Solaris C++) — , . , , , .



, , C++ ( )





, , — . , , . .



dlopen



dlsym



( Windows LoadLibrary



GetProcAddress



). . , , dlopen



.



dlopen



, , ( RTLD_NOW



) ( RTLD_LAZY



). , dlopen



, , , — .



, . , , , . . dlsym



見つかった文字の名前を報告するリテラルパラメータを受け取り、その場所へのポインタを返します(またはNULL



文字が見つからない場合)。



C ++との相互作用



, C++, ?



. dlsym



, , . , , .. .



, , C++ . , — C- , ( ), .



上記を要約すると、次のことに注意してください。通常extern "C"



、エントリポイントに1人の囚人を置く方が適切dlsym



です。このエントリポイントは、C ++クラスのすべてのインスタンスへのポインターを返すファクトリメソッドで、C ++のすべてのチャームにアクセスできます。ライブラリが動的にロードまたはアンロードされた場合、ライブラリに追加でき、リンカによって呼び出される(ロードまたは実行中は関係ありません)いくつかの特殊文字がある



ため、コンパイラはロードされるライブラリのグローバルオブジェクトのデザイナをうまく処理dlopen



できます。ここで、コンストラクタまたはデストラクタへの必要な呼び出しを行うことができます。 Unixでは、これらは関数で_init



あり、_fini



, , GNU , __attribute__((constructor))



__attribute__((destructor))



. Windows — DllMain



DWORD fdwReason



DLL_PROCESS_ATTACH



DLL_PROCESS_DETACH



.



, « », ; « », « » , ( , ). , .



オプショナル



, , , 95% , .



, :





このページに関する有用な提案をしてくれたMike CappとEd Wilsonに感謝します。




Copyright©2004-2005,2009-2010 David Drysdale



このドキュメントは、GNU Free Documentation Licenseバージョン1.1またはFree Software Foundationが発行するそれ以降のバージョンの条件の下で、コピー、配布、および/または変更する許可を与えられています。不変セクション、フロントカバーテキスト、バックカバーテキストはありません。ライセンスのコピーはこちらから入手できます



All Articles