web-dev-qa-db-ja.com

Cヘッダーファイルとコンパイル/リンク

ヘッダーファイルには、_.c_を「呼び出す」_#include_ファイルで使用されるさまざまな関数、構造体などの前方宣言があることを知っていますか?私の知る限り、「権力の分離」は次のように発生します。

ヘッダーファイル:_func.h_

  • 関数の前方宣言を含む

    _int func(int i);
    _

Cソースファイル:_func.c_

  • 実際の関数定義が含まれています

    _#include "func.h"
    
    int func(int i) {
        return ++i ;
    }
    _

Cソースファイル_source.c_(「実際の」プログラム):

_#include <stdio.h>
#include "func.h"

int main(void) {
    int res = func(3);
    printf("%i", res);
}
_

私の質問は、_#include_が単に_.h_が入っているファイルの_#include_の内容をコピーするコンパイラ指令であることを見て、_.c_ファイルはどのように知っているのか関数を実際に実行する方法は?取得できるのはint func(int i);だけなので、実際にどのように機能を実行できますか? funcの実際の定義にアクセスするにはどうすればよいですか?ヘッダーには、「あれは私の定義です!」と言う「ポインター」が含まれていますか?

どのように機能しますか?

27
Aristides

内亜イタチが答えを与えた。 リンカーです。

GNU Cコンパイラgccを使用すると、次のような1ファイルプログラムをコンパイルできます。

gcc hello.c -o hello # generating the executable hello

しかし、例で説明されているように2つ(またはそれ以上)のファイルプログラムをコンパイルするには、以下を実行する必要があります。

gcc -c func.c # generates the object file func.o
gcc -c main.c # generates the object file main.o
gcc func.o main.o -o main # generates the executable main

各オブジェクトファイルには外部シンボルがあります(パブリックメンバと考えることもできます)。関数はデフォルトで外部ですが、(グローバル)変数はデフォルトで内部です。この動作を変更するには、次を定義します

static int func(int i) { # static linkage
    return ++i ;
}

または

/* global variable accessible from other modules (object files) */
extern int global_variable = 10; 

メインモジュールで定義されていない関数の呼び出しを検出すると、リンカーは、呼び出された関数が定義されているモジュールの入力として提供されるすべてのオブジェクトファイル(およびライブラリ)を検索します。デフォルトでは、おそらくあなたのプログラムにリンクされたいくつかのライブラリがあります。それがprintfを使用する方法です。すでにライブラリにコンパイルされています。

本当に興味がある場合は、アセンブリプログラミングを試してください。これらの名前は、アセンブリコードのラベルに相当します。

28
Emil Vatai

それをすべて処理するのはリンカーです。コンパイラは、リンカに対して「この外部シンボルfuncを持っています。解決してください」という特別なシーケンスをオブジェクトファイルに出力します。次に、リンカーはそれを確認し、他のすべてのオブジェクトファイルとライブラリのシンボルを検索します。

14

同じコンパイル単位内で定義のないシンボルの宣言は、そのシンボルのアドレスのプレースホルダーを使用してオブジェクトファイルにコンパイルするようコンパイラーに指示します。

リンカは、シンボルの定義が必要であることを確認し、ライブラリおよび他のオブジェクトファイルでシンボルの外部定義を探します。

リンカが定義を見つけると、元のオブジェクトファイルのプレースホルダは、最終的な実行可能ファイルで見つかったアドレスに置き換えられます。

3
Henrik

ヘッダーは、他の.cファイルは同じプログラム内にありますが、バイナリ形式で配布される可能性のあるライブラリも同様です。 1つの.c別のファイルは、別のファイルに依存するライブラリとまったく同じです。

プログラミングインターフェイスは、実装の形式に関係なくテキスト形式である必要があるため、ヘッダーファイルは懸念事項の分離として意味があります。

他の人が述べたように、関数呼び出しとライブラリとソース(翻訳単位)間のアクセスを解決するプログラムはリンカーと呼ばれます。

リンカーはヘッダーでは機能しません。すべての翻訳単位とライブラリで定義されているすべての名前の大きなテーブルを作成し、それらの名前をそれらにアクセスするコード行にリンクします。 Cを古風に使用すると、実装宣言なしで関数を呼び出すことさえできます。すべての未定義の型はintであると仮定されました。

2
Potatoswatter

通常、次のようなファイルをコンパイルする場合:

gcc -o program program.c

あなたは本当にドライバープログラムを呼び出しています、それは以下をします:

  • cppを使用した前処理(別のステップにするように要求した場合)。
  • cc1を使用したコンパイル(前処理と統合可能)
  • as(ガス、GNUアセンブラー)を使用して組み立てます。
  • collect2を使用したリンク。これもldを使用します(GNUリンカー)。

通常、最初の3つの段階で、単純なオブジェクトファイル(.o拡張子)を作成します。このファイルは、コンパイル単位(.cファイル)をコンパイルすることにより作成され、#includeおよびその他のディレクティブはプリプロセッサに置き換えられます)。

4番目のステージは、最終的な実行可能ファイルを作成するステージです。ユニットのコンパイル後、コンパイラーは、リンカーによって解決される必要がある参照としていくつかのコードをマークします。リンカの仕事は、多くのコンパイル単位を検索し、外部コンパイル単位への参照を解決することです。

2
NlightNFotis