Cのヘッダファイルとライブラリファイル

プログラミング言語による外部モジュールの取り込み方法としては、 C言語や、 Pascalのように、ヘッダファイルを include するパターンと、 Javaや、Go、 Rust などのようにモジュール用ロジックファイル (classファイルや、モジュールファイル)を import するパターンがあります。
(importを実現するキーワードには、言語によって use や、 requreというのも使われていいます。)

includeimport は、同じ外部モジュールを取り込むためのしくみとして使われるもの ですが、実はしくみに大きな違いがあります。

import系では、指定されたファイルにある定義の参照をできるように宣言することが目的になります。 このため、複数のファイルから import しても指定されたファイルの宣言やプログラム実装がコピーされることはありません。

また、定義の参照の宣言があるということは、そのモジュールが利用されることが確定するため、ビルド時に対処のモジュールをリンク対象と判断することができます。

このため、リンク工程において明示的にライブラリを宣言する必要がありません。

include で指定されたファイルは、ビルドのプリプロセスという工程にてファイルの全ての内容が展開されます。

たとえば、以下に実際のプログラム実装を含んだファイルをincludeすると、その実装部分も、指定ファイルに展開されます。

hello.c

export void hello() {
    printf("Hello World");
}

main.c

#include "hello.c"
void main.c() {
    hello();
}

上記の2つのソースは、プリプロセスの工程で以下のように展開される。

main.p

export void hello() {
    printf("Hello World");
}
void main.c() {
    hello();
}

たとえば、複数のソースファイルにて hello.c を include してかつ、それらのソースファイルが、メイン処理のソースファイルから include されている場合、関数 helo が複数定義されている状態になる。

これは、同一シンボルの多重定義としてコンパイル時のエラーとなってしまいます。

しかし、関数 hello を使うプログラムでは、hello の定義宣言がないと、正しくプログラムを実装することができない。そのため、たとえばC言語では、プロトタイプ宣言としくみを導入して、関数などの外部モジュールの定義宣言のみをヘッダファイルと定義するようになった。

上記のhello関数については、以下のような宣言を持つヘッダファイルを作成することになる。

hello.h

void hello();

上記をincludeしたファイルには、定義の宣言しかなく、プログラムの実装は含まれない。 このため、プログラムの実装はinludeするファイルとは別に実装しビルド時のリンク工程において結合するように対応しています。

C言語などでは、このプログラム実装をあつめたものをライブラリとして提供するしくみがあります。

上記が、C言語などのレガシーな言語において、ヘッダファイルとライブラリファイルが別に存在する理由となる。