苦しんで覚えるC言語 20章 複数のソースファイル P404 – P414

複数ファイルを使う理由

ここまでの解説では、main関数と2つ3つの自作関数を掲載して解説をしてきました。
ソースファイルとしては、一つのファイルに全て書き込んできたので、実務を前提に語るならば、ソースファイルやヘッダーファイルは複数扱うことを前提に解説をすべき内容となります。

極小さなプログラムであれば、一つのソースファイルに全て記載をしても問題はありません。
しかし、何万行にも渡るコードを書く場合、一つのファイルに全てを詰め込むのではなく、
機能毎にファイルを分けたり、階層毎にファイルを分けたり、設計思想に応じてファイルの分割をお勧めします。

ソースファイルとヘッダーファイル

初期の頃に「ソースファイルとヘッダーファイルは対にして用意をする」と解説したことがありjます。
基本的には、一つのソースファイルに一つのヘッダーファイルとして用意をすることで、ソースファイルに関連する内容が、対になるヘッダーファイルに書かれていることが分かりやすく、設計ルールが統制されている印象となります。

では、ソースファイルはこれまで書いてきたサンプルのコードの様な処理を書くとして、ヘッダーファイルには何を記述するのでしょうか?
この辺りを解説したいと思います。

度々登場してきたprintf関数ですが、これは標準ライブラリと呼ばれる、C言語で予め用意された処理であることは、初期の頃に解説をしたことがあります。

#include <stdio.h> とファイル冒頭に書いてprintf関数が処理内で使われていることが明記されているのですが、これこそファイルを分割した状態になります。

ソースファイルは 名前 + .c でファイルを構成します。
ヘッダーファイルは、 名前 + .h でファイルを構成します。

stdio.h は、stdioという名前のヘッダーファイルになり、このヘッダーファイルにprintf関数の定義が存在しているのです。

PICマイコンのstdio.hの中身を見てみましょう。
全ての記述するのは大変なので、興味のある方は、MPLAB Xをインストールが完了しているPCの、C:\Program Files\Microchip\xc16\vX.XX\include を確認してみてください。
X.XXはインストール時のバージョンになります。

色々書かれていますが、printfを検索すると、以下の定義が存在しています。

 int printf(const char *__restrict, …);

これまでサンプル例のプログラムを書く際に、自作関数をファイルの冒頭で定義として記載していた内容と同じです。
ヘッダーファイルに printf関数の定義が書かれており、実際の処理コードが別のCソースファイルに書かれてあります。

但し、標準ライブラリの実際のソースコードは、非公開になっていますので視認することができません、、、

とは言え、ヘッダーファイルに書かれている記述の一つに、関数の定義が書かれているということです。

最小限のヘッダーファイル

標準ライブラリでは分かり難いので、自作関数を例に解説してみましょう。

sum.cというソースファイルを以下の記載にて用意。

int sum(int min, int max)
{
   int num;
   num = (mihn + max) * (max - min + 1) / 2;

   return num;
}

sum.hというヘッダファイルを以下の記載にて用意。

int sum(int min, int max);

sum.c と sum.h がそれぞれ一つずつファイルとして用意されました。
ヘッダーファイルの記載は、これまでsum.cのファイルの冒頭に書かれていた内容です。
ソースファイルの先頭に書いても良いですし、ヘッダーファイルとして別ファイルに書いても良いです。

では、敢えてファイルを分けて記載する理由は何でしょうか?
次のコードを例に解説していきます。

main関数については、プロジェクトで一つだけ用意し、複数のファイルから呼び出しされることがないというのが、C言語のそもそものルールなので、main.cに対して、main.hを用意することはしません。その為、以下main.cというソースファイルだけを用意します。

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

int main(void)
{
    int value;
    value = sum(50,100);
    printf("%d\n",value);

    return 0;
}

sum.cというソースファイルを用意する。

int sum(int min, int max)
{
   int num;
   num = (mihn + max) * (max - min + 1) / 2;

   return num;
}

sum.hというヘッダーファイルを用意する。

int sum(int min, int max);

この時点で3つのファイルが存在します。
これらは、全て同じフォルダー内に配置しておきます。

main.cの冒頭に、#include “sum.h” と書かれていますので、
main関数内でsum.hの中に書かれた関数の処理を使うことを明示しています。
つまり、int sum(int min, int max);を使うことを示しているわけです。

実際に、value = sum(50,100);でsum関数が出現していますので、明示した通り使用されていました。
この様に、ヘッダーファイルに書かれた関数がソースファイル内で使われる際にソースファイルの冒頭で#includeにて読み込んで明示をすることがルールとなります。

ファイルを分けて使う理由については、以下になります。
4つめのソースファイルが出現した際に、そのソースファイル内でsum.h内の関数を使いたい場合に、#include “sum.h”とファイル冒頭に書けば、そのソースファイル内でも使用することができます。

4つ目のファイルとして、test.cというソースファイルを用意する。

#include "sum.h"

void test(void)
{
    int test_value;
    test_value = sum(50,100);
}

main.cの先頭に int sum(int min, int max); を書いてしまうと、
test.cの中で使いたい場合に、明示宣言することができません。

つまり、そのファイルだけでしか呼び出すことがない関数であれば、ファイルの冒頭に関数の宣言を書くことで良いのですが、複数のファイルから使用される場合は、ヘッダーファイルとして別ファイルに関数定義を書いておく必要があるということです。

また、機能毎にファイルを分けて用意することで、複数人で設計することがしやすくなります。
例えば、main.cはAさん。test.cはBさん。といった感じで、区切り良い機能毎に分担することも可能です。
一つのファイルに記載をしていると、これが分担できないことになります。

変数の共有

変数をヘッダーファイルに記載することで、複数のファイルで利用することも可能となります。
これは、混乱を招くのでできるだけ使わないことをお勧めします。
しかしながら、概念としては知っておいていただきたいので解説をいたします。

int sum(int min, int max);
int Public;

sum.hとして、int Public; は宣言と実体定義が同時にされていますのでエラーになってしまいます。
sum関数は定義のみ記述されて、実態はsum.c内に書かれておりエラーにはなりません。

extern宣言

int Public;を以下の様に記載することで他のファイルからも実体として認められて利用できるようになります。

int sum(int min, int max);
extern int Public;

参考書では、sum関数にもexternを付けていますが、関数はデフォルトで外部結合の認識を持っていますので、付記しなくても成立します。

extern を変数の前に付記することで、これは定義ではなく、他のファイルのどこかに定義が書かれているよ。と明記することになります。

その為、他のファイルで定義を書かなければなりません。
例えば、以下の様なsum.cというソースファイル内などに記載をします。

int Public;  /* 変数の実体をここで定義した */

int sum(int min, int max)
{
    int num;
    num = (min + max) * (max - min + 1) / 2;

    return num;
}

sum.hには以下の様に書いておきます。

int sum(int min, int max);
extern int Public;

main.c内でPublicという変数が使用された場合、エラーにならずに利用できます。

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

int main(void)
{
    int value;
    value = sum(50,100);
    printf("%d\n",Public);

    return 0;
}

sum.h内でextern int Public;と書いていなければ、
main.cのprintf(“%d\n”,Public);は、Publicが存在しないのでエラーになります。

その為、sum.h内でexternを記載してPublic変数を宣言しておくと、どこかのファイルで定義がされて、他のファイルから使われるよ。と明記しています。

ヘッダーファイルに記述する項目

関数の宣言やextern付きの変数が記述できることは解説しました。
他には、#defineの数値や文字列定義、enumでの定義も記載をします。

つまり、ソースファイルの冒頭で書かれていた数値定義、文字定義、マクロ、enum定義など、
定義関係は全てソースファイルに記載することをお勧めします。

理由は、上記でも書きましたが複数ファイルから使用したい値が利用できるからです。
逆に、一つのソースファイル内でしか使用しないのであれば、ヘッダーファイルに書かないというのも、正しい選択とも言えます。

ヘッダーファイルの重複防ぎ

ヘッダーファイルは、複数のソースファイルから #include sum.h といったように読み込み処理を記載されることが想定されます。
同じヘッダーファイルが1つのソースファイル内で複数回読み込まれることを多重インクルードと言います。

これを防止するために以下の様にヘッダーファイル冒頭に書きます。

#ifdef _INCLUDE_SUM_
#define _INCLUDE_SUM_

int sum(int min, int max);

#endif

#ifdefという記述部分で、別のヘッダファイルで_INCLUDE_SUM_という定義が存在しているかチェックされます。
定義がなければ、#define _INCLUDE_SUM_で定義をした上で、それ以下の記述をコンパイラが処理します。

もしこの後、このヘッダーファイルを処理されようとなったた場合、
既に#define _INCLUDE_SUM_で定義を行ったので、#ifdef _INCLUDE_SUM_のチェックで除外されるという仕組みです。

つまり、1度だけ処理されて2回目以降は無視される仕掛けになります。

ヘッダーファイルに記載される例

最終的にヘッダーファイルには以下の様な内容が記載されることが一般的です。

#ifdef _INCLUDE_SUM_
#define _INCLUDE_SUM_

#define TEST1  10
#define TEST2  20

enum{
   MODE1,
   MODE2,
   MODE3
};

int sum(int min, int max);

#endif

上記は私が適当に例として記載したものですが、#defineの定義、enum定義、関数の定義を必要数分だけ記載することでヘッダーファイルが構成されていきます。

例えば、sum.cに対してsum.hと、対にすべく用意をすると解説しましたが、
sum.c内で書かれている関数処理、使われる#define定義、enum定義は、sum.h内に書きます。
使われる値の定義は、対になるヘッダーファイル内に記載をする。と認識していただければと思います。