MMGamesロゴ  MMGames
Twitterシェアボタン  Facebookシェアボタン   
しんで覚えるC言語
しんで覚えるC言語

分割の定石

変数の共有
前節では、最低限の構成でプログラムを複数ファイルに分割しました。
しかし、共有できたのは関数だけであり、変数の共有は行いませんでした。

複数のソースファイルに分けて開発を行う場合は、
関数だけでなく、変数や定数なども共有する必要が生じてきますが、
前章の方法では、変数を共有することはできません。

たとえば、次のようにヘッダーファイル内で変数を宣言すると、
宣言が重複している、という意味のエラーが表示され、コンパイルできません。

sum.h
/* sum.h */
int sum(int min, int max);
int Public;

このエラーをより正確に理解するには、宣言の意味を理解する必要があります。
これまで、関数にしろ変数にしろ、宣言する、と表現してきましたが、
実は、宣言には2種類の機能があります。

変数や関数の宣言を行うと、コンパイラがその名前と形を記憶します。
これが、宣言と呼ばれる機能です。
そして、同時に、コンパイラは実際に変数や関数を作成します。
これが、定義と呼ばれる機能です。
これまでの変数では、この宣言と定義を常に同時に行っていたのです。

宣言は、変数や関数の形をコンパイラに教えるだけなので、
その形さえ同じであれば、何回宣言しても問題ありません。
しかし、定義では、関数や変数の実体を作成することになります。
同じ関数や変数が何回も作られると区別がつかなくなるためエラーとなります。

プロトタイプ宣言なら
前章では、プロトタイプ宣言だけを記述して成功しましたが、
これは、プロトタイプ宣言は、宣言だけを行い、定義は行わないからです。
したがって、プロトタイプ宣言は(同じ書き方なら)いくつでも書けます。

extern宣言
前項では、変数は宣言と定義を同時に行うため、複数回宣言できないことを説明しました。
この問題を解決するには、宣言だけを複数回行い、定義は1回で済ませる必要があります。
その場合、宣言だけを行うextern(エクスターン)宣言が用意されています。

キーワード
【extern宣言】

宣言だけを行い定義は行わない宣言方法。


extern宣言の使い方は簡単です。ただ、今までの宣言の前に extern と記述するだけです。
次のヘッダーファイルは、関数と変数に対してextern宣言を行っています。

extern宣言
/* sum.h */
extern int sum(int min, int max);
extern int Public;

このextern宣言を使うと、異なるソースファイルで変数を共有することができます。

まずは、ヘッダーファイル内でなんらかの変数をextern宣言します。

sum.h
/* sum.h */
extern int sum(int min, int max);
extern int Public; /* 変数のextern宣言 */

これで、変数Publicは sum.h をインクルードしているすべてのソースファイルで共有できます。
しかし、これだけでは定義がされていないため、変数Publicは作られていません。
そこで、どこか1つのソースファイルの中で普通の宣言を行って実体を作成します。

sum.c
/* sum.c */
int Public; /* 変数の実体の作成 */

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

これで、変数Publicは main.c からも sum.c からも使えるようになります。
次のプログラムは、それを実際に試してみた例です。

main.c
/* main.c */
#include "sum.h"
#include <stdio.h>

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


sum.c
/* sum.c */
int Public;

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

このプログラムの実行結果は次の通りになります。

実行結果
100


必要最低限に
変数の共有は大変便利なテクニックですが、あまり乱用しないでください。
本来、複数のファイルに分割するのは、機能毎に独立させるためです。
しかし、変数の共有を使用すると、同じ変数が使えるようになってしまい、
機能毎に独立させる意味合いが薄れてしまいます。

したがって、可能な限り関数の引数や戻り値を利用し、
変数の共有は、どうしても必要な場合にのみ使用してください。

ヘッダーファイルの重複防ぎ
ここまでは、extern宣言を使用して、重複して定義されることを回避してきましたが、
実は、ヘッダーファイルの重複インクルードそれ自体を防ぐ方法もあります。
それには、#ifndef~#endif疑似命令を使用します。

#ifndef~#endif疑似命令は、ある記号が定義されていなかった場合だけ、
その間に挟まれたプログラムをコンパイルするという記号です。
この性質を利用して、次のようなヘッダーファイルを作成できます。

sum.h
/* sum.h */
#ifndef _INCLUDE_SUM_
#define _INCLUDE_SUM_

int sum(int min, int max);

#endif

このヘッダーファイルでは、最初に、記号_INCLUDE_SUM_が定義されているか調べて、
定義されていなかった場合だけ、その後のプログラムをコンパイルします。
ここでは、後にコンパイルされるプログラムの中で、#define疑似命令を使って、
記号_INCLUDE_SUM_ を定義しているので、このヘッダーファイルが2回目に呼び出された場合、
記号_INCLUDE_SUM_ がすでに定義されていることになり、コンパイルは行われません。

この様にすれば、同じ宣言を何度も行うことがなくなります。
2回目以降はコンパイルされないのでは、1つしか使えなくなるようにも思えますが、
最終的にはすべてのソースファイルは結合されるので、1回コンパイルすれば十分です。

なお、一般には、extern宣言も組み合わせて、次のようにします。
さらに、この様なコメントを入れると、より良いヘッダーファイルが完成します。
この書き方であれば、トラブルが起こりにくいため、常にこの書き方をすることをオススメします。

sum.h
/* sum.h */
#ifndef _INCLUDE_SUM_
#define _INCLUDE_SUM_

/* min~max間の合計値を計算する関数
int min 最小値
int max 最大値
戻り値 int 合計値
*/
extern int sum(int min, int max);

#endif


説明コメントの場所
この様に、関数を説明するコメントをつけるプログラムは良く見かけます。
こうすれば、他人が見た時や、時間がたってから自分が見た時でも、
内容を素早く把握できて便利です。

ただ、筆者は、この様なコメントはヘッダーファイルの中に書くべきで、
ソースファイルの中に書くべきではないと思います。
ヘッダーファイルはその関数を利用するすべての人が読みますが、
ソースファイルの方は誰もが読むとは限らないためです。


自動でできそうな気もするが・・・
ヘッダーファイルは書き方が決まり切っているため、
ソースファイルから自動的に生成することができるような気もします。
実際、他の多くの言語では、自動でやってくれるため、ヘッダーファイルは不要です。

しかし、ヘッダーファイルにはソースファイルの設計書という意味もあります。
先にヘッダーファイルを作り、それに合わせてプログラムを作っていくわけです。
また、ソースファイルには、ヘッダーファイルに書く必要のない、
そのソースファイル固有の関数や変数が使われていることも良くあるため、
自動生成で不要な宣言までヘッダーにしてしまうと、ある種のムダが出てしまいます。

C言語はプログラマーが意識しなければならないことが多いかわりに、
意識して行えば、ムダを大きく減らせるようになっています。



本サイトについて

苦しんで覚えるC言語(苦C)は
C言語入門サイトの決定版です。
C言語の基本機能を体系立てて解説しており、
市販書籍と同等以上の完成度です。

第0部:プログラム概要編
  1. プログラムとは何か?
2章:プログラムの書き方
  1. 書き方のルール
  2. 書き方の慣習
  3. 練習問題2
3章:画面への表示
  1. 文字列の表示
  2. 改行文字
  3. 練習問題3
6章:キーボードからの入力
  1. 入力用の関数
  2. 入力の恐怖
  3. 練習問題6
9章:回数が決まっている繰り返し
  1. 繰り返しを行う文
  2. ループ動作の仕組み
  3. 練習問題9
10章:回数がわからない繰り返し
  1. 回数不明ループ
  2. 入力チェック
  3. 練習問題10
13章:複数の変数を一括して扱う
  1. 複数の変数をまとめて扱う
  2. 配列の使い方
  3. 練習問題13
20章:複数のソースファイル
  1. 最小限の分割
  2. 分割の定石
  3. 練習問題20

コメント
COMMENT

💬 コメント投稿欄を開く