WisdomSoft - for your serial experiences.

4.4 変数の有効範囲

変数の有効範囲について解説します。変数の有効範囲は、宣言された関数内でのみ有効な自動変数と、アプリケーションが実行されている間は常に有効な外部変数によって異なります。

4.4.1 自動変数

変数には使用可能な範囲、すなわち寿命のようなものが存在します。変数の寿命を決定するのは記憶クラスと呼ばれる変数の性質です。複合文の内部で宣言された変数は、プログラムが複合文から抜け出した時に自動的に解放されるという仕組みになっています。こうした変数のことを自動変数と呼びます(または、その局所性からローカル変数と呼びます)。

変数の記憶クラスは変数の可視性につながります。変数の可視性とは、その場所から変数にアクセスできるかどうかということです。例えば、次のようなプログラムはエラーになります。理由は main() 関数からは iValue が見えないからです。

void Function() {
	int iValue;
}
int main() {
	iValue = 10;
	...

Function() 関数では確かに iValue 変数を宣言していますが、これは自動変数なので main() 関数からはアクセスできません。自動変数はそれが宣言されているブロックが実行される時に作られ、ブロックを抜け出す時に削除されます。つまり、変数が宣言されているブロックの外からは、その変数は使えないということです。

逆に考えれば他のブロックの変数は見えないので、次のような複数のブロックで変数名が重複するプログラムは問題ありません。

void Function() {
	int iValue;
}
int main() {
	int iValue;
	...

識別子は常に一意でなければならず、同じ名前が存在する場合はエラーとなります。上のコードは iValue 変数の名前が衝突しているように感じられますが、Function() 関数の iValue は main() 関数からは見えず、同様に main() 関数の iValue は Function() 関数からは見えないので問題はありません。これらの自動変数は、その関数のブロック内でのみ有効だからです。

ここで重要なのは、変数の寿命が関数単位ではなく、複合文のブロック単位であるということです。よく、C言語の初心者は「関数内の変数は、それが宣言された関数の中でのみ有効である」と覚える人がいますが、そうではなく「自動変数は、それが宣言されたブロックの中でのみ有効である」と表現する方が正しいと言えます。これは、次のプログラムから理解することができるでしょう。

コード1
#include <stdio.h>

int main() {
	{
		int iCount;
		for(iCount = 0 ; iCount < 10 ; iCount++)
			printf("1st for : iCount = %d\n" , iCount);
	}
	{
		int iCount;
		for(iCount = 0 ; iCount < 10 ; iCount++)
			printf("2st for : iCount = %d\n" , iCount);
	}
	/*printf("iCount = %d" , iCount); /*エラー*/
	return 0;
}
実行結果
コード1 実行結果

このプログラムは、main() 関数内でさらに 2 つの複合文によるブロックを形成しています。重要なのは、2 つのブロックが内部で同じ名前の変数 iCount を宣言していることです。しかし、これはエラーにはなりません。ブロックの内部で宣言された変数は、そのブロックの外からは見えないためです。

最後の printf() 関数のコメントを取ってコンパイルすればエラーになります。このことからも、ブロックの外からは、ブロックの内部の変数にアクセスできないことを確認できます。

4.4.2 外部変数

変数は、必ずしも複合文の内部で宣言しなければならないというわけではありません。関数の外で変数を宣言することができます。このような場所における宣言を「外部的な」と表現します。例えば、関数自身は常に外部的です。関数は何らかのブロックに内部的に配置されるのではなく、どこからでも呼び出すことができる広域的な存在だからです。

関数の外で宣言された変数を外部変数と呼びます(または、その広域性からグローバル変数と呼びます)。内部変数との最大の違いは、全ての関数からアクセスすることが可能であり、寿命が永続的だという点でしょう。外部変数はプログラムが終了するまで有効です。外部変数は、プログラム全体で共有したい情報を格納するのに適しています。

このように、変数は宣言する位置で記憶クラスと可視性に影響を与えます。関数の外側にある宣言を外部レベルと呼び、関数内の宣言を内部レベルと表現します。これらの違いで、変数の可視性や寿命が異なるため、変数の役割によって区別して使い分けましょう。

コード2
#include <stdio.h>

int iValue = 10;
void Function(void);

int main() {
	Function();
	iValue = 100;
	Function();
	return 0;
}

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

コード2は、外部変数 iValue を宣言しています。この変数は main() 関数からも Function() 関数からもアクセスすることができます。プログラムを実行すれば main() 関数で iValue の値を変更し Function() 関数で iValue を表示すれば、これらが同じ変数を操作していることが確認できます。

ただし、外部変数はどの関数からも自由に値を変更することができるため、大規模なプログラムになると、どこでどの関数がどのような値を代入するかわからなくなり、バグや見苦しいコードを作ってしまう原因となりかねません。通常は、引数や戻り値を使って関数間で情報を受け渡しするべきであり、特別な理由がない限り、外部変数はできるだけ避けるべきであると考えられています。 

外部変数と自動変数の識別子が衝突した場合は、よりローカルな変数が優先されます。この場合はコンパイルエラーにはなりません。

コード3
#include <stdio.h>

int iValue = 1;

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

このプログラムは、外部変数の iValue 変数と、main() 関数内の iValue 変数、そして、ブロック内の iValue 変数の 3 つの変数が宣言されています。これらは同一の名前を持っていますが、宣言する位置が異なっているため、記憶クラスが違います。実行結果から printf() 関数の引数で指定している iValue 変数は、もっともローカル(内側)な変数が優先されていることを確認できます。