web-dev-qa-db-ja.com

Cのヘッダーファイルとソースファイルはどのように機能しますか?

重複の可能性を熟読しましたが、そこに沈む答えはありません。

tl; dr:Cでソースファイルとヘッダーファイルはどのように関連していますか?プロジェクトは、ビルド時に暗黙的に宣言/定義の依存関係を整理しますか?

コンパイラ理解.cファイルと.hファイルの関係を理解し​​ようとしています。

これらのファイルを考えます:

header.h

int returnSeven(void);

source.c

int returnSeven(void){
    return 7;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
    printf("%d", returnSeven());
    return 0;
}

この混乱はコンパイルされますか?現在、ビルドタスクの多くを自動化するCygwinのNetBeans 7.gccを使用して作業を行っています。プロジェクトがコンパイルされると、関連するプロジェクトファイルは、source.cの宣言に基づいてheader.hのこの暗黙的な包含を整理しますか?

50
Dan Lugg

Cソースコードファイルの実行可能プログラムへの変換は、通常、2つの手順で行われます:compilingおよびlinking

最初に、コンパイラはソースコードをオブジェクトファイル(*.o)に変換します。次に、リンカーはこれらのオブジェクトファイルと静的にリンクされたライブラリを受け取り、実行可能プログラムを作成します。

最初のステップで、コンパイラはコンパイル単位を取ります。これは通常、前処理されたソースファイル(つまり、#includesであるすべてのヘッダーの内容を含むソースファイル)です。オブジェクトファイルに。

各コンパイル単位で、使用されるすべての関数は宣言済みである必要があります。これは、関数が存在し、引数が何であるかをコンパイラーに知らせるためです。この例では、関数returnSevenの宣言はヘッダーファイルheader.hにあります。 main.cをコンパイルするとき、宣言にヘッダーを含めて、main.cのコンパイル時にreturnSevenが存在することをコンパイラが認識できるようにします。

リンカがジョブを実行するとき、各関数のdefinitionを見つける必要があります。各関数は、オブジェクトファイルの1つで1回だけ定義する必要があります。同じ関数の定義を含むオブジェクトファイルが複数ある場合、リンカーはエラーで停止します。

関数returnSevensource.cで定義されています(およびmain関数はmain.cで定義されています)。

したがって、要約すると、2つのコンパイル単位があります:source.cおよびmain.c(それに含まれるヘッダーファイルを含む)。これらを2つのオブジェクトファイルsource.oおよびmain.oにコンパイルします。最初のものにはreturnSevenの定義が含まれ、2番目のものにはmainの定義が含まれます。次に、リンカはこれら2つを実行可能プログラムに接着します。

リンケージについて:

外部リンケージおよび内部リンケージがあります。デフォルトでは、関数には外部リンケージがあります。つまり、コンパイラーはこれらの関数をリンカーから見えるようにします。関数staticを作成すると、内部リンケージがあります-関数が定義されているコンパイルユニット内でのみ表示されます(リンカーは、それが存在することを認識しません)。これは、ソースファイルで内部的に何かを行い、プログラムの残りの部分から隠したい関数に役立ちます。

72
Jesper

C言語には、ソースファイルとヘッダーファイルの概念はありません(コンパイラもありません)。これは単なる慣習です。ヘッダーファイルは常にソースファイルへの_#include_ dであることに注意してください。プリプロセッサは、適切なコンパイルが開始される前に、文字通りコンテンツをコピーアンドペーストするだけです。

あなたの例should compile(それにもかかわらず、愚かな構文エラー)。たとえば、GCCを使用すると、最初に次のことを実行できます。

_gcc -c -o source.o source.c
gcc -c -o main.o main.c
_

これにより、各ソースファイルが個別にコンパイルされ、独立したオブジェクトファイルが作成されます。この段階では、returnSeven()は_main.c_内で解決されていません。コンパイラは、オブジェクトファイルを将来的に解決する必要があることを示す方法でマークするだけです。したがって、この段階では、_main.c_がreturnSeven()の-​​definitionを認識できないという問題はありません。 (注:これは、コンパイルするために_main.c_がreturnSeven()の-​​宣言を見ることができる必要があるという事実とは異なります。関数、およびそのプロトタイプです。そのため、_#include "source.h"_で_main.c_を使用する必要があります。

その後、次のことを行います。

_gcc -o my_prog source.o main.o
_

これはリンク 2つのオブジェクトファイルを一緒に実行可能なバイナリにまとめ、シンボルの解決を実行します。この例では、_main.o_はreturnSeven()を必要とし、これは_source.o_によって公開されるため、これは可能です。すべてが一致しない場合、リンカーエラーが発生します。

28

コンパイルには魔法はありません。自動ではありません!

ヘッダーファイルは基本的にコンパイラに情報を提供し、ほとんどコードを提供しません。
通常、その情報だけでは、完全なプログラムを作成するには不十分です。

「hello world」プログラムを考えてみましょう(より単純なputs関数を使用):

_#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}
_

ヘッダーがないと、コンパイラはputs()を処理する方法を知りません(Cキーワードではありません)。ヘッダーにより、コンパイラーは引数と戻り値の管理方法を知ることができます。

ただし、この単純なコードでは、関数の動作方法はどこにも指定されていません。他の誰かがputs()のコードを書き、コンパイルされたコードをライブラリに含めました。そのライブラリのコードは、コンパイルプロセスの一部として、ソース用にコンパイルされたコードに含まれています。

独自のバージョンのputs()が必要だと考えてみましょう

_int main(void) {
    myputs("Hello, World!");
    return 0;
}
_

コンパイラには関数に関する情報がないため、このコードだけをコンパイルするとエラーが発生します。その情報を提供できます

_int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}
_

そして、コードはコンパイルされます---しかし、リンクしません。つまり、myputs()のコードがないため、実行可能ファイルは生成されません。したがって、myputs()のコードを「myputs.c」というファイルに記述します

_#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}
_

both最初のソースファイルと「myputs.c」を一緒にコンパイルすることを忘れないでください。

しばらくすると、「myputs.c」ファイルが関数でいっぱいになり、それらを使用するソースファイルにすべての関数(プロトタイプ)に関する情報を含める必要があります。
1つのファイルにすべてのプロトタイプを記述し、そのファイルを_#include_記述する方が便利です。含めることで、プロトタイプを入力するときに間違いを犯すリスクはありません。

ただし、すべてのコードファイルをコンパイルしてリンクする必要があります。


それらがさらに大きくなると、すでにコンパイルされたすべてのコードをライブラリに入れます...それは別の話です:)

13
pmg

ヘッダーファイルは、ソースファイルの実装に対応するインターフェイス宣言を分離するために使用されます。他の方法で悪用されていますが、これは一般的なケースです。これはコンパイラーのためではなく、コードを書く人間のためです。

ほとんどのコンパイラーは、実際には2つのファイルを別々に見るのではなく、プリプロセッサーによって結合されます。

4
Hack Saw

コンパイラ自体には、ソースファイルとヘッダーファイルの関係に関する特定の「知識」はありません。これらのタイプの関係は通常、プロジェクトファイル(メイクファイル、ソリューションなど)によって定義されます。

指定された例は、正しくコンパイルされるように見えます。両方のソースファイルをコンパイルする必要があり、リンカーは実行可能ファイルを生成するために両方のオブジェクトファイルを必要とします。

2
Mark Wilkins