C言語入門|ヘッダファイルとライブラリで進む分割開発

分割コンパイルを“本物の分業”にするために

分割コンパイルを導入すると、
「変更したファイルだけをコンパイルすればいい」
という効率の良さが手に入ります。

ただし、それを安全かつ正しく行うためには、
もう一歩踏み込んだ仕組みが必要になります。

それが、

  • ヘッダファイルによる宣言の共有
  • オブジェクトファイルやライブラリによる結合

という考え方です。

ここでは、
自作のヘッダファイルとライブラリを使って分割開発を進める基本ルール
を、順を追って見ていきましょう。

ソースファイルを直接インクルードしない理由

分割コンパイルを行うなら、

  • moduleA.c
  • moduleB.c

を、それぞれ独立してコンパイルしたいところです。

ところが、moduleB.c から moduleA の関数を呼び出している場合、
単純に moduleA.c のインクルードを削除すると、コンパイルエラーになります。

理由は単純です。

コンパイラが
「その関数が存在すること」を知らない
からです。

ここで使うのがプロトタイプ宣言

C言語では、

  • 関数の中身(定義)
  • 関数の存在と形(宣言)

を分けて考えます。

プロトタイプ宣言があれば、コンパイラは次のことを理解できます。

  • この関数は、あとでリンク時に見つかるはず。
  • 引数と戻り値の型が正しいかは、今ここでチェックできる。

つまり、定義がなくても宣言だけあればコンパイルは可能なのです。

ヘッダファイルの役割

そこで登場するのが、ヘッダファイルです。

ヘッダファイルには、

  • ほかのファイルから使われる関数や変数の宣言

だけを書きます。

実際の処理内容(定義)は、対応する .c ファイルに書きます。

ヘッダファイルを使った正しい分割例

moduleA.h(宣言だけを書く)

#ifndef __MODULE_A_H__
#define __MODULE_A_H__

void moduleA_task(void);

#endif

moduleA.c(定義を書く)

#include <stdio.h>
#include "moduleA.h"

void moduleA_task(void)
{
    printf("moduleA task executed\n");
}

main.c(宣言をインクルードして利用する)

#include <stdio.h>
#include "moduleA.h"

int main(void)
{
    moduleA_task();
    printf("main finished\n");
    return 0;
}

この構成では、

  • main.c は moduleA_task の存在だけを知っている。
  • moduleA.c に処理の本体がある。

という、きれいな分業状態になっています。

分割コンパイル時のビルド手順

➀各ソースファイルを個別にコンパイル

gcc -c moduleA.c
gcc -c main.c

この時点で、

  • moduleA.o
  • main.o

が生成されます。

➁オブジェクトファイルをリンク

gcc main.o moduleA.o

これで、最終的な実行可能ファイルが完成します。

なぜヘッダファイルが重要なのか

ヘッダファイルを使うことで、次のメリットが得られます。

項目効果
宣言の共有関数の使い方を全員が同じ認識で扱える
分割コンパイル変更のないファイルは再コンパイル不要
分業開発互いの実装を知らなくても作業できる。
保守性修正範囲が明確になる。

大規模開発では、この構造がないと成立しません。

宣言と定義の違いを整理しよう

C言語では、よく似た言葉として
宣言(declaration)定義(definition) があります。

用語意味
宣言存在や外部仕様を示すもの
定義実体そのものを作るもの

関数の場合、

  • 宣言:引数と戻り値の型
  • 定義:処理内容

に相当します。

ヘッダファイルに定義を書いてはいけない理由

もし、ヘッダファイルに関数の定義を書いてしまうと、

  • そのヘッダをインクルードした
  • すべての .c ファイルに
  • 同じ関数定義が埋め込まれる

ことになります。

結果として、リンク時に

同じ関数が複数定義されている。

というエラーが発生します。

これが、

ヘッダファイルには宣言だけ
ソースファイルには定義を書く

という鉄則がある理由です。

ライブラリへの発展

オブジェクトファイルが増えてくると、

  • 便利な機能群をまとめて
  • 再利用できる形にしたくなる。

場面が出てきます。

そのときに使われるのが ライブラリ です。

  • .a や .lib(静的ライブラリ)
  • .so や .dll(動的ライブラリ)

といった形で、
コンパイル済みの機能のかたまりとして配布・利用できます。

まとめ:分割開発の基本ルール

最後に、重要なポイントを一言でまとめます。

ヘッダファイルには宣言を、
ソースファイルには定義を。

このルールを守ることで、

  • 分割コンパイル
  • 分業開発
  • ライブラリ化

という、C言語らしい強力な開発スタイルが実現できます。