これはおそらくばかげた質問ですが、私はここかなり長い間こことWebで検索しており、明確な答えを見つけることができませんでした(私のデューデリジェンスはグーグルでした)。
だから私はプログラミングが初めてです...私の質問は、メイン関数は別のファイルの関数定義(実装)をどのように知るのですか?
例: 3つのファイルがあるとしましょう
//main.cpp
#include "myfunction.hpp"
int main() {
int A = myfunction( 12 );
...
}
-
//myfunction.cpp
#include "myfunction.hpp"
int myfunction( int x ) {
return x * x;
}
-
//myfunction.hpp
int myfunction( int x );
-
プリプロセッサがヘッダーコードをどのようにインクルードするかがわかりますが、ヘッダーとメイン関数は関数定義が存在することをどのようにして知るのでしょうか?
これが明確でない場合、または何か新しいことを非常に誤解している場合は、ここで新しいことをお詫びします
ヘッダーファイルdeclares functions/classes-つまり、コンパイラーが.cpp
ファイルをコンパイルするときに、使用できる関数/クラスを知らせます。
.cpp
ファイルはこれらの関数を定義します。つまり、コンパイラはコードをコンパイルするため、対応する.hpp
ファイルで宣言されているアクションを実行する実際のマシンコードを生成します。
あなたの例では、main.cpp
には.hpp
ファイルが含まれています。プリプロセッサは、#include
を.hpp
ファイルの内容に置き換えます。このファイルは、関数myfunction
が別の場所で定義され、1つのパラメーター(int
)を取り、int
を返すことをコンパイラーに通知します。
したがって、main.cpp
をオブジェクトファイル(拡張子.o)にコンパイルすると、そのファイルに関数myfunction
が必要であることが記録されます。 myfunction.cpp
をオブジェクトファイルにコンパイルすると、オブジェクトファイルにはmyfunction
の定義があるというメモが含まれます。
次に、2つのオブジェクトファイルを一緒に実行可能ファイルにリンクすると、リンカが最後を結びます。つまり、main.o
は、myfunction.o
で定義されているようにmyfunction
を使用します。
それが役に立てば幸い
ユーザーの観点から、コンパイルは2ステップの操作であることを理解する必要があります。
このステップでは、*。cファイルは個別にコンパイルされてseparateになりますオブジェクトファイル。 main.cppがコンパイルされたとき、それはあなたのmyfunction.cppについて何も知りません。彼が知っている唯一のことは、あなたがdeclareこのシグネチャを持つ関数int myfunction( int x )
が他のオブジェクトファイルに存在することです。
コンパイラはこの呼び出しの参照を保持し、オブジェクトファイルに直接含めます。オブジェクトファイルには、「anintでmyfunctionを呼び出す必要があります。これはan intで返されます。インデックスを保持します。すべて extern 呼び出して、後で他とリンクできるようにします。
このステップの間、 linker はオブジェクトファイルのすべてのインデックスを調べ、それらのファイル内の依存関係を解決しようとします。ない場合は、有名なundefined symbol XXX
それから。次に、それらの参照を結果ファイルの実メモリアドレス(バイナリまたはライブラリ)に変換します。
次に、Office Suiteのような巨大なプログラムでこれを行うにはどうすればよいのかを尋ね始めます。まあ、それらは 共有ライブラリ メカニズムを使用します。 Unix/Windowsワークステーションにある「.dll」ファイルや「.so」ファイルでそれらを知っています。プログラムが実行されるまで、未定義のシンボルの解決を延期することができます。
dl * 関数を使用して、未定義のシンボルオンデマンドを解決することもできます。
1。原則
あなたが書くとき:
int A = myfunction(12);
これは次のように翻訳されます。
int A = @call(myfunction, 12);
@call
は、辞書のルックアップと見なすことができます。そして、辞書の類推について考えると、その定義を知る前に、Word(smogashboard?)について確かに知ることができます。必要なのは、実行時に定義が辞書にあることだけです。
2。ABIのポイント
@ callはどのように機能しますか? ABIのため。 ABIは多くのことを説明する方法であり、その中には、特定の関数の呼び出しを実行する方法があります(パラメーターによって異なります)。呼び出し規約は単純です。関数の各引数が見つかる場所を示しているだけです(一部はプロセッサのレジスタにあり、他はスタックにあります)。
したがって、@ callは実際には次のようになります。
@Push 12, reg0
@invoke myfunction
また、関数定義は、その最初の引数(x)がreg0
にあることを認識しています。
。しかし、辞書は動的言語用だったのですか?
そして、あなたはある程度正しいです。動的言語は通常、動的に入力されるシンボル検索用のハッシュテーブルで実装されます。
C++の場合、コンパイラーは翻訳単位(大まかに言えば、前処理されたソースファイル)をオブジェクト(一般には.o
または.obj
)に変換します。各オブジェクトには、参照するシンボルのテーブルが含まれていますが、その定義は不明です。
.undefined
[0]: myfunction
次に、リンカはオブジェクトをまとめて、シンボルを調整します。この時点で2種類のシンボルがあります。
どちらも同じように扱うことができます。
.dynamic
[0]: myfunction at <undefined-address>
そして、コードはルックアップエントリを参照します。
@invoke .dynamic[0]
ライブラリが読み込まれると(たとえば、DLL_Open
)、ランタイムは最終的にwhereシンボルがメモリにマップされていることを認識し、<undefined-address>
を実際のアドレスで上書きします(このため)実行)。
Matthieu M.のコメントで示唆されているように、適切な場所で適切な「関数」を見つけるのはlinker jobです。コンパイル手順は、おおよそ次のとおりです。
プリプロセッサは、ヘッダーファイルの内容をcppファイルに含めます(cppファイルは変換単位と呼ばれます)。コードをコンパイルすると、各翻訳単位が個別に意味的および構文的エラーがチェックされます。翻訳単位にまたがる関数定義の存在は考慮されません。 .objファイルはコンパイル後に生成されます。
次のステップでは、objファイルがリンクされます。使用される関数(クラスのメンバー関数)の定義が検索され、リンクが行われます。関数が見つからない場合は、リンカーエラーがスローされます。
あなたの例では、関数がmyfunction.cppで定義されていない場合でも、コンパイルは問題なく続行されます。リンク手順でエラーが報告されます。
int myfunction(int);
は関数プロトタイプです。 myfunction(0);
を書き込んだときにこの関数が呼び出されていることをコンパイラーが認識できるように、それを使用して関数を宣言します。
そして、ヘッダーとメイン関数は、関数定義が存在することをどのようにさえ知るのですか?
まあ、これは Linker の仕事です。
プログラムをコンパイルすると、プリプロセッサは各ヘッダーファイルのソースコードを、それをインクルードしたファイルに追加します。コンパイラは[〜#〜] every [〜#〜].cpp
ファイルをコンパイルします。結果は、いくつかの.obj
ファイルです。
その後、リンカーが登場します。リンカーは、メインファイルから始めて、すべての.obj
ファイルを取得します。定義のない参照(変数、関数、クラスなど)を見つけると、作成された他の.obj
ファイルでそれぞれの定義を見つけようとしますコンパイル段階で、またはリンク段階の最初にリンカーに提供されます。
今あなたの質問に答えます:各.cpp
ファイルは、マシンコードの命令を含む.obj
ファイルにコンパイルされます。 .hpp
ファイルをインクルードし、別の.cpp
ファイルで定義されている関数を使用すると、リンク段階で、リンカーはそれぞれの.obj
ファイルでその関数定義を探します。それがそれを見つける方法です。