6日でできる 新C言語入門|ソースコード分割のしくみ

プログラムを分けると、役割が見える。
ヘッダファイル、ソースファイル、インクルードガードのしくみを理解して、管理しやすいC言語プログラムを作ろう。

C言語でプログラムを作っていると、最初はmain.cだけで十分に見えるかもしれません。短い練習プログラムであれば、main関数も自作関数も同じファイルに書いて問題なく動かせます。

しかし、プログラムが大きくなると、1つのファイルにすべての処理を書くのはだんだん大変になります。関数の数が増え、処理内容も複雑になると、どこに何の処理があるのか探しにくくなります。

そこで使うのが、ソースコード分割です。

ソースコード分割とは、プログラムを複数のファイルに分けて管理する方法です。C言語では、主にヘッダファイルとソースファイルを使って分割します。

ヘッダファイルは、関数のプロトタイプ宣言や定数、型情報などをまとめるファイルです。拡張子は.hです。
ソースファイルは、関数の本体や実際の処理を書くファイルです。拡張子は.cです。

たとえば、掛け算を行うmultiply関数を別ファイルに分ける場合、multiply.hに関数の宣言を書き、multiply.cに関数の本体を書きます。そしてmain.cではmultiply.hを読み込み、multiply関数を利用します。

このようにファイルを分けると、main.cにはプログラム全体の流れを書き、細かい処理は別のソースファイルに整理できます。プログラムが大きくなっても見通しがよくなり、あとから修正しやすくなります。

この記事では、ソースコード分割の基本的なしくみ、ヘッダファイルの役割、2重インクルードを防ぐインクルードガード、main.c、multiply.h、multiply.cの連携、ビルド時に複数ファイルがどのように1つの実行ファイルになるのかをやさしく解説していきます。

ソースコード分割ではファイルごとに役割を分ける

ソースコード分割で大切なのは、ただファイルを増やすことではありません。大切なのは、役割ごとに分けることです。

たとえば、今回のようにmultiply関数を別ファイルに分ける場合、次のような構成にできます。

ファイル名役割
main.cプログラムの入口。main関数を書き、全体の流れを担当する
multiply.hmultiply関数のプロトタイプ宣言を書く
multiply.cmultiply関数の本体を書く

main.cは、プログラム全体の流れを読むためのファイルです。
multiply.hは、multiply関数をほかのファイルから使えるようにするための情報をまとめるファイルです。
multiply.cは、multiply関数が実際に何をするのかを書くファイルです。

このように分けると、main.cだけを見たときに、プログラム全体が何をしているかを把握しやすくなります。細かい関数の処理は、必要になったときにmultiply.cを確認すればよくなります。

ヘッダファイルは関数の使い方を知らせるファイル

ヘッダファイルには、主に関数のプロトタイプ宣言を書きます。

プロトタイプ宣言とは、関数の戻り値の型、関数名、引数の型をコンパイラへ知らせるための宣言です。

たとえば、2つの整数を掛け算するmultiply関数を使いたい場合、multiply.hには次のような宣言を書きます。

int multiply(int, int);

この宣言を見ることで、main.cはmultiply関数について次の情報を知ることができます。

情報内容
戻り値の型int
関数名multiply
引数int型を2つ受け取る

ヘッダファイルには、関数の本体は書きません。関数の本体はmultiply.cに書きます。

ヘッダファイルは、関数の中身を見せるというよりも、この関数はこのように使えますという情報を共有するためのファイルです。

図:ヘッダファイルとソースファイルの役割

この図から分かること

この図から分かるのは、ソースコード分割では、ヘッダファイルとソースファイルが別々の役割を持っているということです。

main.cはプログラム全体の流れを担当します。multiply.hはmultiply関数の使い方を知らせます。multiply.cはmultiply関数の本体を持ちます。役割を分けることで、プログラムの構成が見やすくなります。

ヘッダファイルには共有したい情報を書く

ヘッダファイルには、複数のソースファイルで共有したい情報を書きます。

代表的な内容は、次のとおりです。

ヘッダファイルに書く内容
関数のプロトタイプ宣言int multiply(int, int);
マクロや定数#define MAX_SIZE 100
構造体の定義typedef structで作る型情報
インクルードガード#ifndef、#define、#endif

今回の例では、multiply関数のプロトタイプ宣言とインクルードガードを書きます。

ヘッダファイルに宣言をまとめておくと、main.cやほかの.cファイルから同じ情報を共有できます。関数の宣言をあちこちに書く必要がなくなり、管理しやすくなります。

インクルードガードは2重インクルードを防ぐしくみ

ヘッダファイルでは、インクルードガードを書くのが基本です。

インクルードガードとは、同じヘッダファイルが複数回読み込まれても、中身が何度も展開されないようにするしくみです。

multiply.hでは、次のように書きます。

#ifndef _MULTIPLY_H_
#define _MULTIPLY_H_

int multiply(int, int);

#endif

この書き方では、最初に_MULTIPLY_H_が定義されているかを確認します。

まだ定義されていなければ、ヘッダファイルの中身を読み込み、次に#defineで_MULTIPLY_H_を定義します。

2回目以降に同じヘッダファイルが読み込まれた場合は、すでに_MULTIPLY_H_が定義されています。そのため、#ifndefの条件が成り立たず、ヘッダファイルの中身は再び読み込まれません。

読み込み回数状態動き
1回目_MULTIPLY_H_は未定義中身を読み込み、_MULTIPLY_H_を定義する
2回目以降_MULTIPLY_H_は定義済み中身を読み込まない

このしくみによって、同じ宣言や定義が何度も読み込まれて起こるエラーを防ぎやすくなります。

インクルードガードの各行の意味

インクルードガードは、最初は少し難しく見えるかもしれません。1行ずつ見ると、役割が分かりやすくなります。

#ifndef _MULTIPLY_H_
#define _MULTIPLY_H_

int multiply(int, int);

#endif

それぞれの意味は、次のようになります。

意味
#ifndef MULTIPLY_H_MULTIPLY_H_がまだ定義されていない場合だけ、次へ進む
#define MULTIPLY_Hこのヘッダファイルを読み込んだ印を付ける
int multiply(int, int);multiply関数のプロトタイプ宣言
#endif#ifndefで始まった範囲を終える

インクルードガードは、ヘッダファイルの多重読み込みを防ぐための安全装置のようなものです。

小さなプログラムでは問題が見えにくい場合もありますが、ファイル数が増えてくると重要になります。ヘッダファイルを作るときは、最初からインクルードガードを書く習慣をつけておくと安心です。

図:インクルードガードは2回目以降の読み込みを防ぐ

この図から分かること

この図から分かるのは、インクルードガードが同じヘッダファイルの重複読み込みを防いでいるということです。

1回目はまだ読み込んだ印がないため、ヘッダファイルの中身が有効になります。2回目以降は、すでに読み込んだ印があるため、中身を再び展開しません。これにより、多重定義によるエラーを防ぎやすくなります。

#includeはファイルの内容を読み込むために使う

ソースコード分割では、#includeの使い方も大切です。

# includeではなく、#includeと続けて書きます。C言語では、ヘッダファイルを読み込むときに使います。

標準ライブラリのヘッダファイルを読み込む場合は、山かっこを使います。

#include <stdio.h>

自作のヘッダファイルを読み込む場合は、ダブルクォーテーションを使います。

#include "multiply.h"

違いを整理すると、次のようになります。

書き方主な用途
#include <stdio.h>標準ライブラリのヘッダファイルを読み込む
#include "multiply.h"自分で作ったヘッダファイルを読み込む

main.cでは、printfを使うためにstdio.hを読み込みます。また、multiply関数を使うためにmultiply.hを読み込みます。

multiply.cでも、multiply.hを読み込んでおくと、関数の宣言と本体が対応しているかを確認しやすくなります。

ファイル構成を確認する

今回のサンプルでは、Lesson44_1というプロジェクトに3つのファイルを用意します。

ファイル内容
multiply.hmultiply関数のプロトタイプ宣言とインクルードガード
multiply.cmultiply関数の本体
main.cmultiply関数を呼び出して結果を表示する

関係を簡単に表すと、次のようになります。

multiply.h
 ├─ main.c が読み込む
 └─ multiply.c が読み込む

main.cはmultiply.hを読み込むことで、multiply関数の使い方を知ります。
multiply.cはmultiply.hを読み込んだうえで、multiply関数の本体を定義します。

multiply.hを作成する

まず、ヘッダファイルmultiply.hを作成します。

このファイルには、インクルードガードとmultiply関数のプロトタイプ宣言を書きます。

プロジェクト/ファイル名: Lesson44_1/multiply.h

#ifndef _MULTIPLY_H_
#define _MULTIPLY_H_

// 2つの整数を掛け算する関数のプロトタイプ宣言
int multiply(int, int);

#endif

このファイルには、multiply関数の本体は書いていません。

ここに書いているのは、multiply関数はint型を2つ受け取り、int型を返す関数です、という情報です。

int multiply(int, int);

この宣言があることで、main.cはmultiply関数を呼び出せるようになります。

multiply.cを作成する

次に、multiply関数の本体を書くmultiply.cを作成します。

multiply.cでは、multiply.hを読み込み、multiply関数の処理を書きます。

プロジェクト/ファイル名: Lesson44_1/multiply.c

#include "multiply.h"

// 2つの整数を掛け算して結果を返す関数
int multiply(int x, int y) {
    return x * y;
}

multiply.cでは、実際にxとyを掛け算して、その結果を返しています。

return x * y;

multiply.hには関数の宣言があり、multiply.cには関数の本体があります。この2つが対応していることが大切です。

main.cを作成する

最後に、main.cを作成します。

main.cでは、標準入出力用のstdio.hと、自作ヘッダファイルのmultiply.hを読み込みます。

そして、multiply関数を呼び出して、長方形の面積を計算します。

プロジェクト/ファイル名: Lesson44_1/main.c

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

int main(void) {
    int width = 4;
    int height = 7;
    int area;

    // multiply関数を使って長方形の面積を計算する
    area = multiply(width, height);

    printf("幅%d、高さ%dの長方形の面積は%dです。\n", width, height, area);

    return 0;
}

実行結果

幅4、高さ7の長方形の面積は28です。

main.cにはmultiply関数の本体はありません。

それでもmultiply関数を呼び出せるのは、main.cがmultiply.hを読み込んでいるためです。

#include "multiply.h"

multiply.hによって、main.cはmultiply関数の戻り値の型や引数の型を知ることができます。

ビルド時には複数の.cファイルがまとめられる

C言語では、複数の.cファイルがある場合、それぞれがコンパイルされます。

今回の例では、main.cとmultiply.cがコンパイルされます。

そのあと、リンクという処理によって、コンパイルされた結果が結び付けられ、1つの実行ファイルになります。

段階内容
コンパイルmain.cとmultiply.cをそれぞれ変換する
リンクmultiply関数の呼び出しと本体を結び付ける
実行完成した実行ファイルを動かす

main.cの中にはmultiply関数の本体がありません。
しかし、multiply.cが同じプロジェクトに含まれていれば、リンク時にmultiply関数の本体が結び付けられます。

そのため、main.cからmultiply関数を呼び出しても、正しく実行できます。

図:複数のファイルはビルド時に結び付けられる

この図から分かること

この図から分かるのは、分割したソースファイルが、ビルド時に1つの実行ファイルとして結び付けられるということです。

main.cはmultiply関数を呼び出しますが、関数の本体はmultiply.cにあります。コンパイルとリンクを通して、呼び出し側と関数本体が結び付くことで、1つのプログラムとして動作します。

Visual Studioでビルドして実行する

Visual Studioでは、プロジェクトにmain.c、multiply.c、multiply.hを追加したあと、ビルドを行います。

ビルドでは、main.cとmultiply.cがコンパイルされます。multiply.hは直接実行されるファイルではありませんが、main.cやmultiply.cに読み込まれて使われます。

ビルドの流れは、次のようになります。

手順内容
1main.c、multiply.c、multiply.hをプロジェクトに追加する
2ソリューションのビルドを実行する
3main.cとmultiply.cがコンパイルされる
4リンクによって1つの実行ファイルが作られる
5デバッグなしで開始して実行結果を確認する

ビルドに成功すると、出力ウィンドウにmain.cやmultiply.cが処理されたことが表示されます。

ビルド開始: プロジェクト: Lesson44_1
main.c
multiply.c
コードを生成中...
ビルド: 成功

実行すると、次のような結果が表示されます。

幅4、高さ7の長方形の面積は28です。

この結果から、main.cからmultiply関数が呼び出され、multiply.cに書いた関数本体が正しく動いていることが分かります。

実行方法

1.「ビルド」 メニューから「ソリューションのビルド」を選択します。

2.ビルドが成功すると、「main.c」「multiply.c」の両方がコンパイルされ、正しくリンクされます。

3.「デバッグ」 メニューから「デバッグなしで開始」を選択します。

ソースコード分割で起こりやすいエラー

ソースコード分割では、ファイルをまたいで関数を使うため、いくつかのミスが起こりやすくなります。

ヘッダファイルを読み込み忘れる

main.cでmultiply関数を使う場合、multiply.hを読み込む必要があります。

#include "multiply.h"

これを忘れると、main.cから見てmultiply関数の情報が分からず、エラーや警告の原因になります。

multiply.cをプロジェクトに追加していない

multiply.cに関数本体を書いていても、Visual Studioのプロジェクトに正しく追加されていないと、ビルド対象になりません。

この場合、main.cからmultiply関数を呼び出していても、リンク時に関数本体が見つからず、エラーになることがあります。

宣言と関数本体が一致していない

multiply.hのプロトタイプ宣言と、multiply.cの関数定義は一致している必要があります。

int multiply(int, int);

int multiply(int x, int y) {
    return x * y;
}

戻り値の型、関数名、引数の数、引数の型が合っていることを確認しましょう。

インクルードガードを書き忘れる

ヘッダファイルにインクルードガードを書かないと、ファイル構成が複雑になったときに多重読み込みが起こりやすくなります。

#ifndef _MULTIPLY_H_
#define _MULTIPLY_H_

int multiply(int, int);

#endif

ヘッダファイルを作るときは、最初からインクルードガードを入れる習慣をつけると安心です。

#includeの書き方も確認しておく

# includeと間を空けず、#includeと書きます。

標準ライブラリを読み込む場合は、次のように山かっこを使います。

#include <stdio.h>

自作ヘッダファイルを読み込む場合は、次のようにダブルクォーテーションを使います。

#include "multiply.h"

この違いは、ヘッダファイルを探す場所の考え方に関係します。標準ライブラリは開発環境側に用意されている場所から探され、自作ヘッダファイルはプロジェクト内などから探されます。

学習段階では、stdio.hは山かっこ、自分で作ったmultiply.hはダブルクォーテーションと覚えると分かりやすいです。

ソースコード分割のしくみを理解すると管理しやすくなる

ソースコード分割は、C言語のプログラムを複数のファイルに分けて管理する方法です。

ヘッダファイルには、関数のプロトタイプ宣言や共有したい情報を書きます。ソースファイルには、関数の本体や具体的な処理を書きます。

今回のLesson44_1では、multiply.h、multiply.c、main.cの3つのファイルを使いました。

multiply.hはmultiply関数の宣言を持ちます。
multiply.cはmultiply関数の本体を持ちます。
main.cはmultiply.hを読み込み、multiply関数を呼び出します。

また、ヘッダファイルにはインクルードガードを書いて、2重インクルードによる問題を防ぎます。

複数の.cファイルは、ビルド時にそれぞれコンパイルされ、リンクによって1つの実行ファイルになります。この流れを理解すると、main.cに関数本体がなくても、別ファイルにある関数を呼び出せる理由が分かります。

ソースコード分割に慣れると、プログラムが大きくなっても、機能ごとにファイルを整理できます。関数の再利用もしやすくなり、保守しやすいC言語プログラムを書けるようになります。