C言語

ファイル分割の基本

C言語では、1つのプログラムを複数のファイルに分けて作ることができます。
これを「ファイル分割」と呼びます。
プログラムが大きくなってくると、1つのファイルの中にすべての変数・関数を書くのは大変です。
また、複数人で作業するときにも、ファイルを分けておくと便利です。

今回は、ファイルを分けて使うための基本を説明します。

ファイルを分ける目的

プログラムが大きくなっても管理しやすくするためです。
ファイルを役割ごとに整理したり、ファイルを分けることで、他の人と違うファイルを修正する場合に同時に作業しやすくなります。
また、別のプロジェクトでも再利用しやすいという利点もあります。

ファイルの分け方は、設計思想によりさまざまです。
設計の考え方は、どこかで記事にできたらと考えています。

今回は、次のように分けるとして想定して、実際にファイルを分けてみます。

ファイル名役割内容
main.cメイン処理メイン関数を記述する
calc.c計算処理計算する関数を記述する
calc.h関数宣言関数の宣言を記述する

main.c

#include <stdio.h>
#include "calc.h"   // 自作のヘッダファイルを読み込む

int main(void)
{
    int a = 5;
    int b = 3;
    int result;

    result = add(a, b);   // 他のファイルの関数を呼び出す
    printf("結果: %d\n", result);

    return 0;
}

calc.c

#include "calc.h"

int add(int x, int y)
{
    return x + y;
}

calc.h

#ifndef CALC_H
#define CALC_H

int add(int x, int y);  // 関数の宣言(プロトタイプ宣言)

#endif

このようにファイルを分けておけば、main.c から add() を呼び出すことができます。
calc.h は、関数の宣言を共有するためのヘッダファイルです。

ヘッダファイルとは

ヘッダファイル(.h)は、「関数や変数の宣言をまとめるファイル」です。
複数の .c ファイルで同じ関数を使いたいとき、宣言を1か所にまとめておくことで、ミスを減らし、メンテナンスもしやすくなります。
どういうことかというと、それぞれ .cファイルに関数宣言を書くと、次のように重複してしまいます。

// main.c
int add(int x, int y);   // 宣言(ここに書く)

// calc.c
int add(int x, int y);   // 宣言(ここにも書く)

コメント

このように、main.c と calc.c の2ファイルに関数宣言を記述する必要があります。
関数が1つだけなら、実際に「int add(int x, int y); を消して #include "calc.h" にしただけで何が違うの?」と思いやすいですが、関数宣言が複数あると、それぞれ関数宣言を記述する必要がでてくるため、ヘッダファイル(.h)の必要性がわかると思います。

ヘッダファイルの補足

書き方の違い

"calc.h"<stdio.h> で書き方の違いがあることが気になりますね。
#include には、2種類の書き方があります。

#include <...> :標準ライブラリをインクルードするときに使います。(コンパイラが決まったフォルダから探す)
#include "..." :自作ヘッダファイルをインクルードするときに使います。(今のプロジェクトフォルダから探す)

" " は「このプロジェクトの中にあるファイル」という意味なので、自分で作ったヘッダファイル(例:calc.h)にはこちらを使います。

2重インクルード防止(インクルードガード)

同じヘッダを何度も読み込むと、「同じ関数を2回宣言した」としてエラーになることがあります。
これを防ぐために、次のような仕組みを入れます

#ifndef CALC_H
#define CALC_H

/* 省略 */

#endif

これは「まだ CALC_H が定義されていなければ中身を読み込む」という意味です。
これをインクルードガードと呼びます。
#ifndef#define などのプリプロセッサ命令は、別の記事で紹介します。
今は「同じヘッダを何回読み込んでも大丈夫にする仕組みがある」と覚えておくだけで十分です。

グローバル変数を分ける場合

今回は、次のように分けるとして想定して、実際にファイルを分けてみます。

ファイル名役割内容
global.c変数の定義グローバル変数の実体を定義
global.h変数宣言変数を宣言
main.cメイン処理グローバル変数を使う
func.c関数処理グローバル変数を変更する関数定義

global.c

int count = 0;   // グローバル変数の定義

global.h

#ifndef GLOBAL_H
#define GLOBAL_H

extern int count;   // グローバル変数の宣言

#endif

main.c

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

void increment(void);

int main(void)
{
    increment();
    printf("count = %d\n", count);
    return 0;
}

func.c

#include "global.h"

void increment(void)
{
    count++;
}

コメント

extern は「他のファイルにある変数や関数を使う」という意味です。
グローバル変数を複数のファイルで共有したいとき、グローバル変数の宣言では、extern を付けます。

なぜ extern がひつようなのか

変数の場合、どこか1か所で実体を作る必要があるからです。
C言語では、変数を「宣言」するだけでなく「定義(メモリを確保)」も行います。

  • int count = 0; → メモリを確保(定義)
  • extern int count; → 他のファイルにある変数を使う(宣言のみ)

もし、2つのファイルに int count = 0; があると、同じ名前の変数を2回定義したことになり、「リンカーエラー」になります。

では、なぜ関数宣言には、extern がいらないのか

変数には extern を付けないと、他のファイルから使えませんでしたね。
でも、関数の場合は extern を書かなくても使うことができます。
実は、C言語では 関数はもともと他のファイルから使えるように作られている ためです。
つまり、関数の宣言は、externを明示的に書かなくても、最初から extern が付いた状態になっています。
これは、セミコロン( ; )の有無で関数定義と関数宣言を区別できるからです。

これに対して、変数の場合は、変数の定義と宣言は見た目が同じなので、区別できません。
このため、明示的に extern を付ける必要があります。

変数の宣言と定義を分ける理由のひとつに、
実体(メモリ上に置かれる本体)がどこにあるのかを明確にする必要がある、という点があります。

コンパイラやリンカは、変数の実体がどこにあるかわからないと、メモリ上のどこに配置すればよいか判断できません。
また、変数を定義する場所(関数の中か、ファイルの先頭か)によって、メモリに割り当てられる領域(スタック、静的領域など)が異なるため、「どこで定義されているか」が非常に重要になります。

グローバル変数と static 変数の違い(おさらい)

変数とスコープ」では、static は、そのファイルの中だけで使えるように制限するためのキーワードでした。
前回は、ファイル分割を説明していなかったので、グローバル変数と、関数の外に書かれたstatic変数の区別がわかりにくかったと思います。
今回、ファイル分割を説明したので、違いがわかったのではないでしょうか。

おさらいすると、

種類宣言場所他のファイルから参照有無メモリに残る期間使いどころ
グローバル変数関数の外(staticなし)参照できるプログラム実行中ずっと共有データ
関数の外のstatic変数関数の外(staticあり)参照できない(そのファイル限定)プログラム実行中ずっとそのファイル内だけ共有データ

static関数(ファイル内だけの関数)

static は、変数だけでなく 関数にも付けることができます。
static を関数につけると、その関数は同じファイルの中からしか呼び出せなくなります。
つまり、static関数 = ファイル限定の関数です。

使いどころとしては、

  • 他のファイルに見せる必要がない「補助関数」を作りたいとき
  • 外部から誤って呼ばれるのを防ぎたいとき(安全性アップ)
  • モジュール内部でのみ使う関数を隠したいとき

static関数も、関数定義したら、static関数を使う前に、先に関数宣言が必要になります。

#include <stdio.h>

static void printMessage(void);  // ← 宣言

int main(void)
{
    printMessage();  // OK
    return 0;
}

static void printMessage(void)   // ← 定義
{
    printf("Hello!\n");
}

このように、関数定義と関数宣言の最初に、sataticを付けます。

まとめ

  • 大きなプログラムは、役割ごとにファイルを分ける。
  • 関数の宣言はヘッダファイル(.h) にまとめて書く。
  • 変数を他ファイルで使うときは extern を付ける。
  • 関数には extern を書かなくてもOK(自動的に外部から使える)。
  • 他ファイルから見えないようにしたいときは static を付ける。
  • static関数 はファイル内だけで使える関数。
  • 宣言と定義では、static の有無をそろえること。
  • 同じ変数を複数のファイルで定義しないように注意する。

-C言語