WisdomSoft - for your serial experiences.

7.13 外部レベル宣言

extern 記憶クラス指定子を用いて関数の外で宣言されたグローバル変数を複数のソースファイルで共有する方法や、あるいは static 記憶クラス指定子を用いて宣言されたファイル内に留め、たのソースファイルからは参照できないようにする方法を紹介します。

7.13.1 グローバル変数への参照

4.4 変数の有効範囲」で説明したように、関数の外の空間を外部レベルと呼び、外部レベルで行われた宣言を外部レベル宣言と呼びます。逆に、関数の中を内部レベルと呼び、自動変数などの宣言を内部レベル宣言と呼びます。内部レベルとは異なり、外部レベルの可視空間はファイル全体です。そのC言語ソースファイルのあらゆる関数から、外部レベルで宣言された変数などにアクセスすることができます。

外部レベル宣言の可視性は、ファイル スコープと考えられます。つまり、外部レベルで宣言された変数や関数は、その宣言を行ったソースファイルのどの位置からでもアクセスすることができるというものです。ただし、宣言を行う前の関数などからアクセスすることはできません。そのため、通常はファイルの先頭で必要な宣言を行ってしまいます。関数は常に外部レベル宣言であり、関数の内部で関数を宣言することはできません。

宣言が行われる前の位置から、変数や関数にアクセスすることができないというのは、例えば次のような場合です。

void Function() {
	printf("%s\n" , str);
}
char *str;

この場合、Function() 関数から str 変数を参照していますが、str 変数は Function() 関数よりも後ろで宣言されています。そのため Function() 関数からは str 変数を認識することはできません。str は未定義のシンボルと解釈されてしまうでしょう。関数であれば、こうした問題はプロトタイプの宣言で克服することができました。

宣言の位置が不明な外部レベルの変数にアクセスしたい場合は、記憶クラス指定子の一種である extern 指定子を用います。この記憶クラスを持つ変数は、プログラムを構成するC言語ソースファイルのいずれかで、外部レベルで定義されている同名の変数に対する参照になります。つまり、関数宣言子の変数版のようなものだと考えてください。extern で宣言された変数は、その時点ではメモリを確保(初期化)しませんが、ソースのどこかに同名の外部レベル変数が存在することをコンパイラに通達します。

extern 指定子
extern  変数名 ...

extern 指定子を持つ変数の宣言では初期化はできません。extern で宣言された変数は、他の場所で実体が定義されていることを表しています。

また、外部レベル宣言で auto と register を使うことはできません。外部レベル宣言はその性質上自動変数になることはありえませんし、長期的にCPUのレジスタを占有することも難しいと考えられるからです。

コード1
#include <stdio.h>

extern char *str;
int main() {
	printf("%s\n" , str);
	return 0;
}
char *str = "Kitty on your lap";
実行結果
コード1 実行結果

コード1では、ファイルの先頭に extern 記憶クラスを持つ変数 str を宣言しています。str 変数の実体は main() 関数よりも後ろで初期化されていますが、extern によって参照が作成されているため、main() 関数から str 変数を参照することが許されています。このプログラムは問題なくコンパイルすることができるでしょう。

extern 記憶クラス指定子は内部レベルの変数にも指定することができます。この場合、変数が宣言されているブロック内で、指定された外部レベルへの参照が有効になります。当然、ブロックの外側からは extern を指定した変数が見えないため参照は使えません。

コード2
#include <stdio.h>

int main() {
	{
		extern char *str;
		printf("%s\n" , str);	/* OK */
	}
	/*printf("%s\n" , str);	/*エラー*/
	return 0;
}
char *str = "Kitty on your lap";
実行結果
コード2 実行結果

コード2では、main() 関数内でネストしている無名のブロックで extern 記憶クラスを持つ変数 str を宣言しています。ブロック内からは、この extern を指定した str への参照を利用することができます。ブロックの外では str への参照を保持しないため、main() 関数のコメント化されている printf() 関数をコンパイルすればエラーとなるでしょう。局所的にグローバル変数を可視化したい場合に有効です。

7.13.2 静的なグローバル変数

static 記憶クラス指定子を持つグローバル変数は、その変数が宣言されているファイル内でのみ参照できることを表しています。実は、複数の C 言語ファイルをコンパイルした場合、他のファイルから extern 記憶クラスを使ってグローバル変数に参照することができます。例えば次のような場合です。

コード3
int iValue = 0xFF;
void Function(void);

int main() {
	Function();
	return 0;
}
コード4
#include <stdio.h>

extern int iValue;
void Function() {
	printf("iValue = %d\n" , iValue);
}
実行結果
コード3 コード4 実行結果

コード3コード4を同時にコンパイルした場合、コード4は iValue 変数を extern 記憶クラス指定子を用いて宣言し、コード3の iValue 変数に参照しています。複数のファイルをコンパイルする方法については、使っているコンパイラのヘルプを参照してください。Boland C++ Compiler で複数のソースファイルをコンパイルする場合は、1 つ以上のスペースで区切ってファイル名を指定してください。

>BCC32 test1.c test2.c

他のファイルの特定のグローバル変数にアクセスするには extern を使えば解決できるのですが、場合によってはこれが好ましくないこともあります。ある程度の信頼性が必要なグローバル変数や、そのファイル内の関数間の情報伝達にのみ使う目的のグローバル変数であれば、他の開発者が作成した可能性のある異なるソースファイルからアクセスされることは好ましくありません。

このため、特に公開の必要がないグローバル変数には static 記憶クラス指定子を設定し、外部のファイルからはアクセスできないように仕組みます。そうすることで、識別子の競合など、不要なエラーや誤解によるバグを避けることができます。

コード5
#include <stdio.h>

static int iValue = 0xFF;

int main() {
	printf("iValue = %d\n" , iValue);
	return 0;
}
実行結果
コード4 実行結果

コード5の iValue 変数は static 記憶クラスを持つグローバル変数です。この変数はこのファイルからのみ参照することが可能であり、外部のファイルから extern などを使って参照することはできません。こうすることで、識別子の予期しない衝突を避けることができます。コード5コード4を同時にコンパイルしても、コード4からは iValue を参照できないため、エラーとなるでしょう。