18

さて、今朝まで、私はこれらの用語の間で完全に混乱していました。うまくいけば、私は違いを持っ​​ていると思います。

まず、混乱は、プリプロセッサが関数を含むコードにヘッダーファイルをすでにインクルードしているため、リンカーはどのライブラリ関数をアセンブラ/コンパイラによって生成されたオブジェクトファイルにリンクするのかということでした。混乱の一部は、主にヘッダーファイルとライブラリの違いについての私の無知が原因で発生しました。

少しグーグルしてスタックオーバーフロー(用語は?:p)した後、ヘッダーファイルにはほとんど関数宣言が含まれていますが、実際の実装はライブラリと呼ばれる別のバイナリファイルにあります(まだ100%ではありません)これについては確かです)。

したがって、次のプログラムで想定します。-

#include<stdio.h>
int main()
{
      printf("whatever");
      return 0;
}

プリプロセッサは、ヘッダーファイルの内容をコードにインクルードします。コンパイラ/コンパイラ+アセンブラがその作業を行い、最後にリンカがこのオブジェクトファイルを実際にprintf()動作を保存している別のオブジェクトファイルと結合します。

私の理解は正しいですか?私は遠く離れているかもしれません...だから私を助けてくれませんか?

編集:私はいつもC++STLについて疑問に思っていました。それが正確に何であるか、それらすべてのヘッダーのコレクション、または何であるかについて、それは常に私を混乱させましたか?応答を読んだ後、STLはオブジェクトファイル/オブジェクトファイルに似たものであると言えますか?

pow()また、などの関数の関数定義をどこで読めるか考えましたsqrt()。ヘッダーファイルを開いても何も見つかりませんでした。では、ライブラリ内の関数定義はバイナリで読み取れない形式ですか?

4

3 に答える 3

23

AC ソース ファイルは、(1) プリプロセッサ ディレクティブを検索してそれらのアクションを実行するプリプロセッサ ユーティリティによって C ソース コードが処理されるプリプロセッサ ステージと、(2) 処理された C ソース コードが次に処理されるコンパイル ステージの 2 つの主要なステージを通過します。オブジェクト コード ファイルを生成するために実際にコンパイルされます。

プリプロセッサは、テキスト操作を行うユーティリティです。プリプロセッサ ディレクティブを含む可能性のあるテキスト (通常は C ソース コード) を含むファイルを入力として受け取り、見つかったディレクティブをテキスト入力に適用してテキスト出力を生成することにより、ファイルの修正バージョンを出力します。

プリプロセッサがテキスト操作を行っているため、ファイルは C ソース コードである必要はありません。make私は、プリプロセッサ ディレクティブをメイク ファイルに含めることができるようにすることで、ユーティリティを拡張するために使用される C プリプロセッサを見てきました。C プリプロセッサ ディレクティブを含む make ファイルは、C プリプロセッサ ユーティリティを介して実行され、結果の出力がフィードされmakeて、make ターゲットの実際のビルドが実行されます。

ライブラリとリンク

ライブラリとは、さまざまな機能のオブジェクト コードを含むファイルです。複数のソース ファイルを 1 つのファイルにコンパイルするときに、それらの出力をパッケージ化する方法です。多くの場合、ライブラリ ファイルはヘッダー ファイル (インクルード ファイル) と共に提供され、通常は .h ファイル拡張子が付いています。ヘッダー ファイルには、関数宣言、グローバル変数宣言、およびライブラリに必要なプリプロセッサ ディレクティブが含まれています。したがって、ライブラリを使用するには、#includeディレクティブを使用して提供されるヘッダー ファイルをインクルードし、ライブラリ ファイルとリンクします。

ライブラリ ファイルの優れた機能は、ソース コード自体ではなく、ソース コードのコンパイル済みバージョンを提供することです。一方、ライブラリ ファイルにはコンパイル済みのソース コードが含まれているため、ライブラリ ファイルの生成に使用されるコンパイラは、独自のソース コード ファイルのコンパイルに使用されるコンパイラと互換性がある必要があります。

一般的に使用されるライブラリには 2 つのタイプがあります。最初の古いタイプはスタティック ライブラリです。2 つ目以降はダイナミック ライブラリ (Windows ではダイナミック リンク ライブラリまたは DLL、Linux では共有ライブラリまたは SO) です。2 つの違いは、ライブラリ内の関数が、ライブラリ ファイルを使用している実行可能ファイルにバインドされている場合です。

リンカーは、さまざまなオブジェクト ファイルとライブラリ ファイルを使用して実行可能ファイルを作成するユーティリティです。外部関数またはグローバル関数または変数が C ソース ファイルで使用される場合、その時点で関数または変数のアドレスを挿入する必要があることをリンカーに伝えるために、一種のマーカーが使用されます。

C コンパイラは、コンパイルするソースの内容のみを認識しており、オブジェクト ファイルやライブラリなどの他のファイルの内容は認識していません。したがって、リンカーの仕事は、さまざまなオブジェクト ファイルとライブラリを取得し、マーカーを実際の接続に置き換えることで、パーツ間の最終的な接続を作成することです。したがって、リンカーはさまざまなコンポーネントを「リンク」するユーティリティであり、オブジェクト ファイルとライブラリ内のグローバル関数または変数のマーカーを、そのグローバル関数または変数用に生成された実際のオブジェクト コードへのリンクに置き換えます。

リンカ段階では、静的ライブラリと動的ライブラリまたは共有ライブラリの違いが明らかになります。スタティック ライブラリを使用すると、ライブラリの実際のオブジェクト コードがアプリケーションの実行可能ファイルに含まれます。動的ライブラリまたは共有ライブラリを使用する場合、アプリケーションの実行可能ファイルに含まれるオブジェクト コードは、アプリケーションの実行時に共有ライブラリを見つけて接続するためのコードです。

場合によっては、同じグローバル関数名が複数の異なるオブジェクト ファイルまたはライブラリで使用されることがあるため、リンカは通常、最初に検出したものだけを使用し、見つかった他のものについては警告を発します。

コンパイルとリンクのまとめ

したがって、C プログラムのコンパイルとリンクの基本的なプロセスは次のとおりです。

  • プリプロセッサ ユーティリティは、コンパイルする C ソースを生成します

  • コンパイラは、C ソースをオブジェクト コードにコンパイルし、一連のオブジェクト ファイルを生成します。

  • リンカーは、さまざまなオブジェクト ファイルとライブラリを実行可能ファイルにリンクします。

上記は基本的なプロセスですが、動的ライブラリを使用する場合、特に生成されるアプリケーションの一部に生成中の動的ライブラリがある場合は、さらに複雑になる可能性があります。

ローダー

アプリケーションが実際にメモリにロードされ、実行が開始される段階もあります。オペレーティング システムは、アプリケーションの実行可能ファイルを読み取ってメモリにロードし、アプリケーションの実行を開始するローダーというユーティリティを提供します。実行可能ファイルの開始点またはエントリ ポイントは実行可能ファイルで指定されるため、ローダーが実行可能ファイルをメモリに読み取った後、エントリ ポイントのメモリ アドレスにジャンプして実行可能ファイルの実行を開始します。

リンカが遭遇する可能性のある問題の 1 つは、実際のメモリ アドレスを必要とするオブジェクト コード ファイルを処理しているときに、マーカーに遭遇する場合があることです。ただし、アドレスはアプリケーションがメモリ内のどこにロードされているかによって異なるため、リンカは実際のメモリ アドレスを知りません。そのため、リンカーは、ローダーが実行可能ファイルをメモリにロードし、実行を開始する準備をしているときに、ローダー ユーティリティが修正するものとしてマークします。

仮想アドレスから物理アドレスへのマッピングまたは変換をサポートするハードウェアを備えた最新の CPU では、この実際のメモリ アドレスの問題が問題になることはめったにありません。各アプリケーションは同じ仮想アドレスにロードされ、ハードウェア アドレス変換によって実際の物理アドレスが処理されます。ただし、アドレス変換用のメモリ管理ユニット (MMU) ハードウェア サポートがない古い CPU やマイクロコントローラーなどの低コストの CPU では、この問題に対処する必要があります。

エントリ ポイントと C ランタイム

最後のトピックは、C ランタイムとmain()実行可能なエントリ ポイントです。

C ランタイムは、C で記述されたアプリケーションのエントリ ポイントを含む、コンパイラの製造元によって提供されるオブジェクト コードです。main()関数は、アプリケーションを記述するプログラマによって提供されるエントリ ポイントですが、これはローダーが認識するエントリ ポイントではありません。このmain()関数は、アプリケーションの開始後に C ランタイムによって呼び出され、C ランタイム コードによってアプリケーションの環境が設定されます。

C ランタイムは標準 C ライブラリではありません。C ランタイムの目的は、アプリケーションのランタイム環境を管理することです。標準 C ライブラリの目的は、一連の便利なユーティリティ関数を提供して、プログラマが独自に作成する必要がないようにすることです。

ローダーがアプリケーションをロードし、C ランタイムによって提供されるエントリ ポイントにジャンプすると、C ランタイムは、アプリケーションに適切なランタイム環境を提供するために必要なさまざまな初期化アクションを実行します。これが完了すると、C ランタイムはmain()関数を呼び出して、アプリケーション開発者またはプログラマーが作成したコードの実行を開始します。main()が戻ったとき、またはexit()関数が呼び出されたときに、C ランタイムは、アプリケーションをクリーンアップして閉じるために必要なアクションを実行します。

于 2012-08-29T15:25:38.463 に答える
3

これは非常に一般的な混乱の原因です。何が起こっているのかを理解する最も簡単な方法は、簡単な例を挙げることだと思います。ライブラリについてはしばらく忘れて、次のことを検討してください。

$ cat main.c
extern int foo( void );
int main( void ) { return foo(); }
$ cat foo.c
int foo( void ) { return 0; }
$ cc -c main.c
$ cc -c foo.c
$ cc main.o foo.o

宣言extern int foo( void )は、ライブラリのヘッダー ファイルとまったく同じ機能を実行しています。 foo.oライブラリの機能を実行しています。この例と、どちらcc main.ccc main.o機能しない理由を理解すれば、ヘッダー ファイルとライブラリの違いが理解できます。

于 2012-08-29T13:48:20.197 に答える
2

はい、ほぼ正解です。ただし、リンカーはオブジェクト ファイルだけでなくライブラリもリンクします。この場合、C 標準ライブラリ (libc) がオブジェクト ファイルにリンクされます。残りの仮定は、コンパイル段階とヘッダーとライブラリの違いについて正しいようです。

于 2012-08-29T12:24:05.407 に答える