web-dev-qa-db-ja.com

コンパイル/リンクプロセスはどのように機能しますか?

コンパイルとリンクのプロセスはどのように機能しますか?

(注:これは Stack OverflowのC++ FAQ へのエントリであることを意味しています。この形のFAQ、それから これをすべて始めたmetaへの投稿 がそれを行う場所になるでしょう。質問は C++チャットルーム で監視されます。そこではFAQという考えが最初に出てきたので、あなたの答えはアイデアを思いついた人たちに読まれてください。)

376
Tony The Lion

C++プログラムのコンパイルには3つのステップがあります。

  1. 前処理:プリプロセッサはC++ソースコードファイルを受け取り、#includes、#defines、およびその他のプリプロセッサ指令を扱います。このステップの出力は、プリプロセッサディレクティブのない「純粋な」C++ファイルです。

  2. コンパイル:コンパイラはプリプロセッサの出力を受け取り、そこからオブジェクトファイルを生成します。

  3. リンク:リンカは、コンパイラによって生成されたオブジェクトファイルを受け取り、ライブラリまたは実行可能ファイルを生成します。

前処理

プリプロセッサは、#include#defineのように、プリプロセッサ指令を処理します。これはC++の構文にとらわれないため、注意して使用する必要があります。

#includeディレクティブをそれぞれのファイルの内容(通常は宣言のみ)で置き換え、マクロ(#define)の置換を行い、#if#ifdefおよび#ifndefに応じてテキストのさまざまな部分を選択することによって、一度に1つのC++ソースファイルを処理します。ディレクティブ.

プリプロセッサは一連の前処理トークンを処理します。マクロ置換は、トークンを他のトークンに置き換えることとして定義されています(演算子##は、意味があるときに2つのトークンをマージすることを可能にします)。

結局のところ、プリプロセッサは、上記の変換から生じるトークンのストリームである単一の出力を生成します。また、各行がどこから来たのかをコンパイラに伝える特別なマーカーも追加されているので、それらを使用して適切なエラーメッセージを生成することができます。

この段階で、#ifおよび#errorディレクティブを賢く使用すると、エラーが発生する可能性があります。

編集

コンパイルステップは、プリプロセッサの各出力に対して実行されます。コンパイラは純粋なC++ソースコード(現在はプリプロセッサディレクティブなし)を解析し、それをアセンブリコードに変換します。次に、そのコードをマシンコードにアセンブルし、何らかの形式(ELF、COFF、a.outなど)で実際のバイナリファイルを生成する、基礎となるバックエンド(ツールチェーンのアセンブラ)を呼び出します。このオブジェクトファイルには、入力で定義されたシンボルのコンパイル済みコード(バイナリ形式)が含まれています。オブジェクトファイル内のシンボルは名前で呼ばれます。

オブジェクトファイルは、定義されていないシンボルを参照できます。これは、宣言を使用し、それに対する定義を提供しない場合に当てはまります。コンパイラはこれを気にせず、ソースコードが整形式である限り、オブジェクトファイルを生成します。

コンパイラは通常、この時点でコンパイルを中止させます。これを使用すると、各ソースコードファイルを別々にコンパイルできるので非常に便利です。これがもたらす利点は、単一のファイルを変更するだけであれば、すべてを再コンパイルする必要がないことです

生成されたオブジェクトファイルは、後で簡単に再利用できるように、静的ライブラリと呼ばれる特別なアーカイブに入れることができます。

この段階で、構文エラーや失敗したオーバーロード解決エラーなどの「通常の」コンパイラエラーが報告されます。

リンク

リンカは、コンパイラが生成したオブジェクトファイルから最終的なコンパイル出力を生成するものです。この出力は、共有(または動的)ライブラリ(および名前は似ていますが、前述の静的ライブラリとあまり一般的にはなっていません)または実行可能ファイルのいずれかです。

未定義シンボルへの参照を正しいアドレスに置き換えて、すべてのオブジェクトファイルをリンクします。これらの各シンボルは、他のオブジェクトファイルまたはライブラリで定義できます。それらが標準ライブラリ以外のライブラリで定義されている場合は、それらについてリンカに通知する必要があります。

この段階で最も一般的なエラーは定義の欠落または重複した定義です。前者は、定義が存在しない(つまり、それらが書かれていない)か、またはそれらが存在するオブジェクトファイルまたはライブラリがリンカに渡されなかったことを意味します。後者は明らかです。同じシンボルが2つの異なるオブジェクトファイルまたはライブラリで定義されています。

499

このトピックはCProgramming.comで議論されています。
https://www.cprogramming.com/compilingandlinking.html

これは、作者が書いたものです。

コンパイルは、実行可能ファイルを作成するのとまったく同じではありません。代わりに、実行可能ファイルを作成することは、コンパイルとリンクという2つのコンポーネントに分割された多段階プロセスです。実際には、たとえプログラムが "うまくコンパイル"したとしても、リンク段階でのエラーのために実際には動作しないかもしれません。ソースコードファイルから実行可能ファイルまでの全体的なプロセスは、ビルドと呼ばれる方がよいでしょう。

編集

コンパイルとは、ソースコードファイル(.c、.cc、または.cpp)の処理と「オブジェクト」ファイルの作成を指します。この手順では、ユーザーが実際に実行できるものは作成されません。代わりに、コンパイラは単にコンパイルされたソースコードファイルに対応する機械語命令を生成するだけです。たとえば、3つの別々のファイルをコンパイルする(ただしリンクしない)場合、3つのオブジェクトファイルが出力として作成され、それぞれ.oまたは.objという名前が付けられます(拡張子はコンパイラによって異なります)。これらの各ファイルには、ソースコードファイルの機械語ファイルへの翻訳が含まれています - しかし、それらをまだ実行することはできません。あなたはそれらをあなたのオペレーティングシステムが使用できる実行可能ファイルに変える必要があります。それがリンカが入ってくるところです。

リンク

リンクとは、複数のオブジェクトファイルから単一の実行可能ファイルを作成することです。このステップでは、リンカが未定義の関数(一般的にはmain自体)について文句を言うのが一般的です。コンパイル中に、コンパイラが特定の関数の定義を見つけられなかった場合は、その関数が別のファイルで定義されていると見なします。そうでない場合は、コンパイラがそれを知る方法はありません。一度に複数のファイルの内容を調べることはありません。一方、リンカは複数のファイルを調べて、言及されていない関数の参照を見つけようとします。

コンパイルとリンクの手順が別々にあるのはなぜかと疑問に思うかもしれません。第一に、そのように物事を実装する方がおそらく簡単です。コンパイラがその機能を実行し、リンカがその機能を実行します - 関数を別々にしておくことによって、プログラムの複雑さが軽減されます。もう1つの(より明白な)利点は、ファイルが変更されるたびにコンパイル手順をやり直す必要なしに大きなプログラムを作成できることです。代わりに、いわゆる「条件付きコンパイル」を使用して、変更されたソースファイルだけをコンパイルすることが必要です。その他の点では、オブジェクトファイルはリンカにとって十分な入力です。最後に、これは事前にコンパイルされたコードのライブラリを実装することを簡単にします。オブジェクトファイルを作成し、他のオブジェクトファイルと同じようにそれらをリンクするだけです。 (各ファイルが他のファイルに含まれている情報とは別にコンパイルされるという事実は、偶然にも「個別コンパイルモデル」と呼ばれます。)

条件コンパイルの利点を最大限に引き出すには、前回コンパイルしたときから変更したファイルを試して覚えておくよりも、プログラムを手助けする方が簡単です。 (もちろん、対応するオブジェクトファイルのタイムスタンプよりも大きいタイムスタンプを持つすべてのファイルを再コンパイルすることもできます。)統合開発環境(IDE)で作業している場合は、すでにこれを気にする必要があります。コマンドラインツールを使用している場合は、makeと呼ばれる便利なユーティリティがあり、ほとんどの* nixディストリビューションに付属しています。条件付きコンパイルに加えて、それはあなたのプログラムの異なるコンパイルを可能にするなど、プログラミングのための他のいくつかの素晴らしい機能を持っています - 例えば、デバッグのために冗長な出力を生成するバージョンがあるなら。

コンパイル段階とリンク段階の違いを知っていると、バグを見つけやすくなります。通常、コンパイラエラーは構文的なものです - セミコロンの欠如、余分な括弧です。リンクエラーは通常、定義の欠落または複数の定義と関係があります。関数または変数がリンカから複数回定義されているというエラーが発生した場合は、2つのソースコードファイルが同じ関数または変数を持っていることがエラーであることを示しています。

32
user2003323

標準的には

  • 翻訳単位は、ソースファイル、インクルードヘッダー、およびソースファイルから、条件付き包含プリプロセッサディレクティブによってスキップされたソース行を除いたものです。

  • 規格は翻訳の9段階を定義しています。最初の4つは前処理に対応し、次の3つはコンパイル、次の1つはテンプレートのインスタンス化(インスタンス化単位の生成)、最後の1つはリンク.

実際には、8番目のフェーズ(テンプレートのインスタンス化)は、コンパイルプロセス中に行われることがよくありますが、一部のコンパイラはそれをリンクフェーズに遅らせ、一部を2つに展開します。

23
AProgrammer

細かいのは、CPUがメモリアドレスからデータをロードし、データをメモリアドレスに格納し、処理された命令シーケンス内の条件付きジャンプで、メモリアドレスから命令を順次実行することです。これら3つのカテゴリの命令の各々は、機械命令で使用されるべきメモリセルへのアドレスを計算することを含む。機械語命令は含まれる特定の命令に応じて可変長であり、そして機械語コードを構築するときにそれらの可変長を一緒につなぐので、アドレスを計算しそして構築することに含まれる2段階プロセスがある。

最初に、各セルの内容が正確にわかるようになる前に、可能な限りメモリの割り当てをレイアウトします。バイト、ワード、命令やリテラルを構成するもの、そしてデータを特定します。メモリの割り当てを開始し、プログラムを作成するための値を作成していきます。そして、戻ってアドレスを修正する必要がある場所を書き留めます。その場所に、メモリのサイズを計算し続けることができるように、単に位置を埋めるためにダミーを入れます。例えば、私たちの最初の機械語は1つのセルを取ります。次のマシンコードは、1つのマシンコードセルと2つのアドレスセルを含む3つのセルを取ることがあります。これで、アドレスポインタは4になりました。オペコードであるマシンセルに何が入るかはわかっていますが、データが配置される場所がわかるまで、アドレスセルに入るものを計算するのを待つ必要があります。そのデータのマシンアドレス。

ソースファイルが1つしかない場合、コンパイラは理論的にはリンカなしで完全に実行可能なマシンコードを生成できます。ツーパスプロセスでは、任意のマシンロードまたはストア命令によって参照されるすべてのデータセルに対するすべての実際のアドレスを計算できます。そしてそれはどんな絶対ジャンプ命令によっても参照される絶対アドレスのすべてを計算することができます。これは、Forthのコンパイラのように、リンカを使用せずに単純なコンパイラを使用する方法です。

リンカは、コードのブロックを別々にコンパイルできるようにするものです。これはコード構築の全体的なプロセスをスピードアップすることができます、そして、ブロックが後で使用される方法でいくらかの柔軟性を可能にします。

つまり、コンパイラが出力するのは、まだ完全には構築されていない大まかなマシンコードですが、すべてのサイズがわかるようにレイアウトされているので、絶対アドレスすべての位置を計算することができます。コンパイラは、名前とアドレスのペアであるシンボルのリストも出力します。シンボルは、モジュール内のマシンコード内のメモリオフセットを名前と関連付けます。オフセットは、モジュール内のシンボルのメモリ位置までの絶対距離です。

それがリンカーに到達するところです。リンカは最初にこれらのマシンコードのブロックすべてを端から端までまとめてスラップし、それぞれのブロックの開始位置を書き留めます。次に、モジュール内の相対オフセットと、より大きいレイアウト内のモジュールの絶対位置を足し合わせて、固定されるアドレスを計算します。

私はこれを単純化しすぎてあなたがそれを理解しようと試みることができるようにし、そして私は故意に混乱の一部であるオブジェクトファイル、シンボルテーブルなどの専門用語を使わなかった。

14
Elliptical view

URLを見てください。 http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
C++の完全なコンパイルプロセスは、このURLで明確に紹介されています。

9
Charles Wang

GCCはC/C++プログラムを4つのステップで実行可能ファイルにコンパイルします。

たとえば、 "gcc -o hello.exe hello.c"は次のように実行されます。

1。前処理

GNU Cプリプロセッサ(cpp.exe)を介した前処理。これにはヘッダー(#include)が含まれ、マクロが展開されます(#define)。

cpp hello.c> hello.i

結果の中間ファイル "hello.i"は展開されたソースコードを含みます。

2。コンパイル

コンパイラは、前処理されたソースコードを特定のプロセッサ用のアセンブリコードにコンパイルします。

gcc -S hello.i

-Sオプションは、オブジェクトコードの代わりにアセンブリコードを生成するように指定します。結果のアセンブリファイルは "hello.s"です。

3。議会

アセンブラ(as.exe)は、アセンブリコードをオブジェクトファイル "hello.o"内のマシンコードに変換します。

as -o hello.o hello.s

4。リンカ

最後に、リンカ(ld.exe)がオブジェクトコードとライブラリコードをリンクして実行可能ファイル "hello.exe"を生成します。

ld -o hello.exe hello.o ...ライブラリ...

2
kaps