超重要なファイルスコープ

う〜ん。こんな感じでいいのかなぁ……?
どうしたの、レオ君。なにか考えごとかしら?
あ、ユキ先輩!……実は、今書いているプログラムがある程度大きくなってきたので、ソースファイルを分割しようと思っているんですけど。
分割コンパイルね。
はい。でも、イマイチこれでちゃんと動くのか自信がなくて。……分割コンパイルをするときに、気を付けておくと良いポイントとかってあるんでしょうか?
そうねぇ……。「スコープ」をきちんと意識すると、メンテナンスがしやすいプログラムになるわよ。
スコープですか!?

C言語のスコープ(変数や関数などの有効範囲)には、以下の3つのスコープがあります。

C言語のスコープ

  • グローバルスコープ:どこからでもアクセスできる
  • ファイルスコープ:モジュール内のみアクセスできる
  • ローカルスコープ(ブロックスコープ):ブロック内のみアクセスできる

たしか、分割コンパイルをするときはグローバルスコープの変数をできるだけ少なくした方がいいんですよね?
そのとおりよ。
でも、ファイルスコープの変数っていうのは、あまりピンとこないです……。
じゃあ、まずはファイルスコープについて説明するわね。

ファイルスコープにする必要がある変数とは、「モジュール内の複数の関数からアクセスされるが、モジュール外からはアクセスされない変数」です。

例えば、次のようなC言語のプログラムがあったとします。ドキュメントファイルを扱うためのモジュールです(細かい処理は省略しています)。

document.h
#ifndef document_h
#define document_h

void OpenDocument(const char *pDocumentName);
void CloseDocument(void);

#endif /* document_h */
document.c
#include <assert.h>
#include "document.h"

typedef enum {
  Closed,
  Open
} DocumentOpenStatus;

static DocumentOpenStatus openStatus = Closed;

void OpenDocument(const char *pDocumentName) {
  assert(openStatus == Closed);

  /* ドキュメントを開く処理(省略) */

  openStatus = Open;
}

void CloseDocument(void) {
  assert(openStatus == Open);

  /* ドキュメントを閉じる処理(省略) */

  openStatus = Closed;
}

OpenDocument()では、すでにドキュメントが開いている状態での呼び出しを禁止するためにassert()を使っています。同様に、CloseDocument()ではドキュメントが開いていない状態での呼び出しを禁止しています。

assert()については別のトピックで詳しく説明します。

上記のモジュールは、main()から次のように呼びだされます。

main.c
#include <stdlib.h>
#include "document.h"

int main(void) {
  OpenDocument("document-name");
  CloseDocument();

  return EXIT_SUCCESS;
}

では本題に入りましょう。

「document.c」には、openStatusという変数がありますね。これがファイルスコープ変数です。モジュール内の複数の関数からアクセスされるため、関数の外側で定義されています。

そして、キーワードstaticを付けることによって、ほかのモジュールからは見えないようにしています。(staticを付けないとグローバルスコープ、つまりほかのモジュールからも見える変数になります。)

ここで重要なのは、この変数は「他のモジュールから見えない」という点よ。

もしopenStatusがグローバルだったら、いつその値が変更されてしまうか分かりません。ファイルスコープにすることによって、変数の値が予期せぬタイミングで変更されないようにしているのです。

また、この変数はモジュール「document.c」の支配下にあります。

ファイルスコープ変数にしておけば、変数の有効範囲はモジュール内だけになります。そのため「document.c」を修正するときには、変数がモジュール内でどのように振る舞うかにだけ注意を払えばよいのです。

これがファイルスコープを使う本質的な理由よ。

情報系の仕事をしている人なら「情報隠蔽」という言葉を聞いたことがあるでしょう。これはまさに「コーディングするときに同時に気を配る必要がある範囲をいかにして狭めるか」という問いへの答えです。

なるほど〜。変数のスコープはなるべく狭めて、情報を隠蔽するといいんですね。
そういうこと。C言語では、そのためにファイルスコープ変数を使いこなせるようになっておくのがとても重要なのよ。
ここがポイント!
モジュール分割をするときは、変数のスコープを意識しましょう。ファイルスコープ変数は、モジュール分割の設計における「情報隠蔽」の手段です。
……あれれ?じゃあ、グローバルスコープの変数は、どんなときに使うのがいいんでしょうか?
ふふふ。じゃあ次のトピックでは、グローバルスコープの変数について説明するわね。