この記事の目的は、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コンパイラが行うこと
- リンカの機能:パート1
- オペレーティングシステムは何をしますか?
- リンカの機能:パート2
- 画像を補完する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
問い合わせてください)が、重要な情報は各文字のクラスとそのサイズ(存在する場合)です。 クラスにはさまざまな意味があります。
- クラスUは、前述の「空のスペース」と同じあいまいな参照を示します。 このクラスには、
fn_a
とz_global
2つのオブジェクトがあります。 (nm
バージョンによっては、この場合*UND*
またはUNDEF
になるセクションが出力される場合があります。) - クラスtおよびTは、定義されているコードを示します。 tとTの違いは、関数がファイル内でローカル( t )かそうでないか( T )、つまり 関数が
static
として宣言されたかどうか。 繰り返しますが、一部のシステムでは、.text
などのセクションが表示される場合があります。
- クラスdおよびDには、初期化されたグローバル変数が含まれます。 さらに、静的変数はクラスdに属します。 セクション情報が存在する場合、それは.dataになります。
- 初期化されていないグローバル変数については、静的な場合はbを 、そうでない場合はBまたはCを取得します。 この場合のセクションは、おそらく.bssまたは* COM *です。
ソース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セグメントと呼ばれます。
つまり、グローバル変数用のスペースは、ディスクに保存されている実行可能ファイルに割り当てることができます。 初期化された変数の場合、その初期値を保存する必要がありますが、初期化されていない変数の場合、サイズを保存するだけです。
お気づきかもしれませんが、これまでオブジェクトファイルとリンカに関するすべての議論で、グローバル変数についてのみ話してきました。 前述のローカル変数と動的に占有されるメモリについては言及しませんでした 。
このデータは、プログラムの実行中に有効期間が開始および終了するため、リンカーの介入を必要としません。これは、リンカーがすでにジョブを実行したよりもずっと後のことです。 ただし、説明を完全にするために、次のことを簡単に示します。
- ローカル変数は、 スタックと呼ばれるメモリ領域に配置されます 。このメモリ領域は、さまざまな関数が呼び出されて実行されると拡大および縮小します。
- 動的に割り当てられたメモリはheapと呼ばれるメモリ領域から取得され、malloc関数はこの領域の空き領域へのアクセスを制御します。
図を完成させるには、実行中のプロセスのメモリ空間がどのようなものかを追加する価値があります。 ヒープとスタックは動的にサイズを変更できるため、スタックが一方向に成長し、ヒープが反対方向に成長することは非常に一般的です。 したがって、スタックとヒープが中間のどこかで出会った場合にのみ、プログラムは空きメモリ不足のエラーを出します(この場合、プログラムのメモリ空間は本当にいっぱいになります)。
リンカが行うこと; パート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つの方法はすべて同じライブラリに混在させることができます)。
- ソースコードで、次のような文字を宣言します
__declspec(dllexport)
。
__declspec(dllexport) int my_exported_function(int x, double y)
- リンカーコマンドを実行するときは、オプションを使用します
LINK.EXE
export:
symbol_to_export
LINK.EXE /dll /export:my_exported_function
- このファイルにエクスポートする文字を含むセクションを含めることにより、モジュール定義ファイル(DEF)をリンカーにフィードします(オプションを使用
/DEF:
def_file
)EXPORT
。
EXPORTS my_exported_function my_other_exported_function
C ++がこのmishmashに接続されるとすぐに、これらのオプションの最初のオプションが最も簡単になります。この場合、コンパイラは名前の装飾を引き受けるためです。
.LIB
およびその他のライブラリ関連ファイル
Windowsライブラリに関連する2番目の困難に直面します。エクスポートされたシンボルに関する情報は、リンカーが他のシンボルと関連付ける必要がありますが、それ自体には含まれていませんDLL
。代わりに、この情報は対応する
.LIB
ファイルに含まれています。
.LIB
に関連付けられているファイルは
DLL
、どの(エクスポートされた)文字が
DLL
その場所とともに含まれているかを説明します。を使用するバイナリは、文字を正しく関連付けるためにファイルに
DLL
アクセスする必要があります
.LIB
。
物事をさらに混乱させるために、拡張機能は
.LIB
静的ライブラリにも使用されます。
実際、Windowsライブラリに何らかの形で関連する可能性のあるさまざまなファイルがいくつかあります。とともに
.LIB
ファイル、および(オプションの)
.DEF
ファイルを使用すると、Windowsライブラリに関連付けられている次のすべてのファイルを表示できます。
- レイアウト出力ファイル
- library
.DLL
:実際のライブラリコード。このファイルは、ライブラリを使用するバイナリが(実行時に)必要とします。
- library
.LIB
:結果のシンボルのどこにどのシンボルがあるかを記述する「ライブラリインポート」ファイルDLL
。このファイルは、DLL
一部の文字のみをエクスポートする場合に生成されます。文字がエクスポートされない場合、.LIB
ファイルにはポイントがありません。このファイルはリンク中に必要です。
- library
.EXP
:コンパイル済みライブラリの「エクスポートファイル」。循環依存関係のあるバイナリが存在する場合に必要です。
- library
.ILK
:/INCREMENTAL
, , . .
- library
.PDB
:/DEBUG
, , .
- library
.MAP
:/MAP
, .
- library
- :
- library
.LIB
: « »,DLL
, .
- library
.LIB
: , , ..LIB
- library
.DEF
: «», , .
- library
.EXP
: , ,LIB.EXE
.LIB
. .
- library
.ILK
: ; 上記を参照してください。
- ライブラリ
.RES
:実行可能ファイルで使用されるさまざまなGUIウィジェットに関する情報を含むリソースファイル。これらのリソースは、最終的なバイナリに含まれています。
- library
これは、これらすべての追加ファイルに含まれるほぼすべての情報が単にライブラリ自体に追加される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 .
- X.
LIB.EXE
(LINK.EXE
),X.LIB
,LINK.EXE
.X.DLL
,X.EXP
.
-
Y
,X.LIB
, ,Y.DLL
Y.LIB
.
- 最後に、ライブラリを
X
完全に作成します。これX.EXP
は、最初のステップで取得した追加のファイルを使用して、ほぼ通常どおりに発生します。このステップでの通常のことは、リンカが使用Y.LIB
および生成するものX.DLL
です。異常-X.LIB
このファイルはファイルの存在から明らかなように、最初のステップですでに作成されているため、リンカーは作成ステップをスキップし.EXP
ます。
しかし、周期的な依存関係を回避するような方法でライブラリを再編成することは確かに優れています...
画像を補完する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% , .
, :
- John Levine, Linkers and Loaders : , , . - ( ) .
- Mach-O Mac OS X.
- Peter Van Der Linden, Expert C Programming : , , , C, , C, .
- Scott Meyers, More Effective C++ : 34 , C C++ ( )
- Bjarne Stroustrup, The Design and Evolution of C++ : 11.3 C++ .
- Margaret A. Ellis & Bjarne Stroustrup, The Annotated C++ Reference Manual : 7.2c
- ELF format reference [PDF]
- Linuxでの軽量の実行可能ファイルの作成に関する2つの興味深い記事と、特に最小限のHello World。
- 悪名高いUlrich Drepperの「共有ライブラリの書き方」[PDF]には、 ELFと転送に関する詳細が含まれています。
このページに関する有用な提案をしてくれたMike CappとEd Wilsonに感謝します。
Copyright©2004-2005,2009-2010 David Drysdale
このドキュメントは、GNU Free Documentation Licenseバージョン1.1またはFree Software Foundationが発行するそれ以降のバージョンの条件の下で、コピー、配布、および/または変更する許可を与えられています。不変セクション、フロントカバーテキスト、バックカバーテキストはありません。ライセンスのコピーはこちらから入手できます。