web-dev-qa-db-ja.com

C / C ++ヘッダーと実装ファイル:それらはどのように機能しますか?

これはおそらくばかげた質問ですが、私はここかなり長い間こことWebで検索しており、明確な答えを見つけることができませんでした(私のデューデリジェンスはグーグルでした)。

だから私はプログラミングが初めてです...私の質問は、メイン関数は別のファイルの関数定義(実装)をどのように知るのですか?

例: 3つのファイルがあるとしましょう

  • main.cpp
  • myfunction.cpp
  • myfunction.hpp

//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 );

-

プリプロセッサがヘッダーコードをどのようにインクルードするかがわかりますが、ヘッダーとメイン関数は関数定義が存在することをどのようにして知るのでしょうか?

これが明確でない場合、または何か新しいことを非常に誤解している場合は、ここで新しいことをお詫びします

40
nickelpro

ヘッダーファイル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を使用します。

それが役に立てば幸い

63
Ed Heal

ユーザーの観点から、コンパイルは2ステップの操作であることを理解する必要があります。


第1ステップ:オブジェクトのコンパイル

このステップでは、*。cファイルは個別にコンパイルされてseparateになりますオブジェクトファイル。 main.cppがコンパイルされたとき、それはあなたのmyfunction.cppについて何も知りません。彼が知っている唯一のことは、あなたがdeclareこのシグネチャを持つ関数int myfunction( int x )が他のオブジェクトファイルに存在することです。

コンパイラはこの呼び出しの参照を保持し、オブジェクトファイルに直接含めます。オブジェクトファイルには、「anintmyfunctionを呼び出す必要があります。これはan intで返されます。インデックスを保持します。すべて extern 呼び出して、後で他とリンクできるようにします。


2番目のステップ:リンク

このステップの間、 linker はオブジェクトファイルのすべてのインデックスを調べ、それらのファイル内の依存関係を解決しようとします。ない場合は、有名なundefined symbol XXX それから。次に、それらの参照を結果ファイルの実メモリアドレス(バイナリまたはライブラリ)に変換します。


次に、Office Suiteのような巨大なプログラムでこれを行うにはどうすればよいのかを尋ね始めます。まあ、それらは 共有ライブラリ メカニズムを使用します。 Unix/Windowsワークステーションにある「.dll」ファイルや「.so」ファイルでそれらを知っています。プログラムが実行されるまで、未定義のシンボルの解決を延期することができます。

dl * 関数を使用して、未定義のシンボルオンデマンドを解決することもできます。

15
Coren

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>を実際のアドレスで上書きします(このため)実行)。

5
Matthieu M.

Matthieu M.のコメントで示唆されているように、適切な場所で適切な「関数」を見つけるのはlinker jobです。コンパイル手順は、おおよそ次のとおりです。

  1. コンパイラーは各cppファイルに対して呼び出され、シンボルテーブルを使用してオブジェクトファイル(バイナリコード)に変換します。これにより、関数名(c ++では名前がマングルされます)をオブジェクトファイル内の場所に関連付けます。
  2. リンカは一度だけ呼び出されます。つまり、パラメータ内のすべてのオブジェクトファイルで呼び出されます。 symbol tablesのおかげで、関数呼び出しの場所を1つのオブジェクトファイルから別のオブジェクトファイルに解決します。 1つのmain()関数がどこかに存在する必要があります。リンカが必要なものをすべて見つけたときに、最終的にバイナリ実行可能ファイルが生成されます。
4
yves Baumes

プリプロセッサは、ヘッダーファイルの内容をcppファイルに含めます(cppファイルは変換単位と呼ばれます)。コードをコンパイルすると、各翻訳単位が個別に意味的および構文的エラーがチェックされます。翻訳単位にまたがる関数定義の存在は考慮されません。 .objファイルはコンパイル後に生成されます。

次のステップでは、objファイルがリンクされます。使用される関数(クラスのメンバー関数)の定義が検索され、リンクが行われます。関数が見つからない場合は、リンカーエラーがスローされます。

あなたの例では、関数がmyfunction.cppで定義されていない場合でも、コンパイルは問題なく続行されます。リンク手順でエラーが報告されます。

4
Ram

int myfunction(int);は関数プロトタイプです。 myfunction(0);を書き込んだときにこの関数が呼び出されていることをコンパイラーが認識できるように、それを使用して関数を宣言します。

そして、ヘッダーとメイン関数は、関数定義が存在することをどのようにさえ知るのですか?
まあ、これは Linker の仕事です。

2
LihO

プログラムをコンパイルすると、プリプロセッサは各ヘッダーファイルのソースコードを、それをインクルードしたファイルに追加します。コンパイラは[〜#〜] every [〜#〜].cppファイルをコンパイルします。結果は、いくつかの.objファイルです。
その後、リンカーが登場します。リンカーは、メインファイルから始めて、すべての.objファイルを取得します。定義のない参照(変数、関数、クラスなど)を見つけると、作成された他の.objファイルでそれぞれの定義を見つけようとしますコンパイル段階で、またはリンク段階の最初にリンカーに提供されます。
今あなたの質問に答えます:各.cppファイルは、マシンコードの命令を含む.objファイルにコンパイルされます。 .hppファイルをインクルードし、別の.cppファイルで定義されている関数を使用すると、リンク段階で、リンカーはそれぞれの.objファイルでその関数定義を探します。それがそれを見つける方法です。

1
atoMerz