まず、C++ プログラムがどのようにコンパイルされるかを大まかに理解します。main.cpp
プロジェクトに 、foo.cpp
、およびの 3 つのファイルがある場合、次foo.h
のようにプロジェクトをコンパイルできます。
g++ main.cpp foo.cpp
これら 2 つのファイルは別々にコンパイルされます。プリプロセッサとコンパイラは、これらのファイル間の関係についてまったく気にしません。前処理とコンパイルmain.cpp
の結果はオブジェクト ファイルになり、前処理とコンパイルfoo.cpp
の結果は別のオブジェクト ファイルになります。
関係が考慮されるのは、最後のステップであるリンクのステップだけです。リンカーmain
は で定義されていることを確認し、それmain.cpp
をプログラムの開始点にします。リンカーは、内部で定義されてmain.cpp
いる関数への呼び出しがあることを確認し、それらをリンクします。foo
foo.cpp
そのため、コンパイラに渡すファイルごとに、次の 2 つの主な手順があります。
前処理 - ファイルは非常に原始的な方法で処理され、その結果トークンが前処理されます。これらのトークンの一部は、または#
などの で始まる前処理ディレクティブです。これらのディレクティブが実行されます。ディレクティブを実行すると、名前付きファイルの内容が現在のファイルにコピーされるだけです。#include
#define
#include
前処理段階では、いわゆる翻訳単位が生成されます。
コンパイル - ファイルは構文的および意味的に分析され、オブジェクト ファイルに変換されます。ここから、コードが正確に何をしようとしているのか、それが正しいかどうかを気にし始めます。宣言されていない名前を使用しようとすると、コンパイラが警告します。ただし、別の翻訳単位で定義されている可能性があるため、定義されていない名前を使用できることがよくあります。
このコンパイル段階では、オブジェクト ファイルが生成されます。
この後、生成されたオブジェクト ファイルは、それらの間の参照を解決することによって結合されます。に対応するオブジェクト ファイルが で定義されてmain.cpp
いる関数を使用する場合、それらは互いにリンクされます。foo
foo.cpp
わかりやすくするために、例を見てみましょう。次のソース ファイルがあるとします。
そのため、コマンド ラインでmain.cpp
andをコンパイラに渡します。foo.cpp
ヘッダー ファイルをコンパイラに渡さないことに注意してください。それらはファイルによってのみ含まれ.cpp
ます。.cpp
また、ファイルには通常、他のファイルは含まれないことに注意してください.cpp
。各.cpp
ファイルは個別にコンパイルされ、後でリンクされます。
したがって、前処理段階の後、2 つの翻訳単位があります。
の内容がfoo.h
各ファイルにコピーされていることに注意してください。これで、2 つの完全に有効な翻訳単位ができました。1 つ目は関数を定義しmain
、宣言のみの関数を呼び出すだけfoo
です。2 番目は最初に宣言しfoo
、その直後に定義します。
次に、翻訳単位がコンパイルされてオブジェクト ファイルが生成されます。これらのファイルは、多くの場合、 および と呼ばれmain.o
ますfoo.o
。次に、リンカーは、ファイル内の特定の未解決の参照を調べます。たとえば、foo
inの呼び出しを見つけて、main.o
まだ定義されていないことを確認します。そのため、他のオブジェクト ファイルを調べて、それを見つけることができるかどうかを確認しfoo.o
ます。