変数のスコープ
変数の有効範囲の違い
変数は、その宣言を書く場所によって有効範囲が異なります。
これを変数のスコープといいます。
変数のスコープ外からは、その変数を参照することはできなくなります。
#include <stdio.h>
//グローバル変数
int g_global = 1;
int main()
{
//ローカル変数
int local1 = 2;
{
//ローカル変数
int local2 = 3;
printf("%d\n", g_global); //1
printf("%d\n", local1); //2
printf("%d\n", local2); //3
}//変数local2の寿命はここまで
printf("%d\n", g_global); //1
printf("%d\n", local1); //2
//アクセスできない
//printf("%d\n", local2)
getchar();
}
このサンプルコードでは変数を三つ使用しています。
コードを実行すると、1、2、3、1、2の順で数字が表示されます。
4行目ではmain関数の外側、どの関数にも属さない場所で変数g_global
が宣言されています。
これをグローバル変数と言います。
グローバル変数は、プログラムのどこからでもアクセスが可能な変数となります。
9行目の変数local1
は今まで使ってきた普通の変数です。
ある関数内で宣言した変数は、その関数内でのみ使用可能です。
これをローカル変数と言います。
10~17行目は、関数内に波括弧{}
でブロックが作られています。
ブロック内で宣言された変数は、そのブロック内でのみ有効なローカル変数となります。
12行目の変数local2
は、17行目でブロックを抜けた時点で消滅します。
14、15、16行目ではグローバル変数と、それぞれのローカル変数の値を表示しています。
内側のスコープからは、その外側のスコープにある変数を参照することができます。
(ただし、その行以前に宣言された変数のみ)
反対に、外側のスコープからは内側のスコープの変数は見えなくなります。
17行目以降では変数local2
の値を取り出すことはできません。
ローカル変数は、有効範囲内の処理を抜けるとメモリ上から消滅します。
そのため変数のスコープは変数の寿命、とも呼ばれます。
(ただし、変数のスコープと寿命は必ずしも一致しない場合もあります)
これは関数の場合も同様です。
#include <stdio.h>
int Test()
{
int num1 = 3;
int num2 = 5;
return num2;
}//関数の終了後は変数num1とnum2は消滅する
int main()
{
int num = Test();
printf("%d", num);
getchar();
}
自作関数Test
内で宣言されている変数num1
、num2
は、関数の終了と共にメモリ上から消去されます。
上記のコードでは変数num2
を関数の戻り値に指定していますが、関数の戻り値には値をコピーしたものが渡されるため、これは問題ありません。
宣言前の変数は使用できない
変数が使用できるのは、その変数が宣言された以降の行です。
宣言行よりも手前の行でその変数を使用することはできません。
#include <stdio.h>
int main()
{
pirntf("%d", num); //NG
int num = 3;
pirntf("%d", num); //OK
getchar();
}
古いコンパイラでは、ローカル変数の宣言はすべて関数の先頭で行わなければならない場合があります。
ブロック内で同名の変数宣言
内側のスコープで外側のスコープと同じ名前を宣言することもできます。
#include <stdio.h>
int main()
{
int local = 1;
{
int local = 2;
printf("%d", local); //2
}
printf(",%d", local); //1
getchar();
}
このコードを実行すると「2,1」と表示されます。
内側のスコープで、すでに外側のスコープに存在する変数と同名の変数を宣言すると、外側にある同名変数は見えなくなります。
(アクセスできなくなる)
しかし値が上書きされるわけではないので、10行目で表示される変数local
の値は「1」のままです。
つまり、同名の変数は「現在の行より前にある、最も内側のブロックで宣言されたもの」に対しての読み書きが可能です。
ひとまとめの処理ごとにブロックでコードを分割することで、同名の変数であってもそれぞれ独立して使用することができます。
ブロック内で宣言した変数の寿命はそのブロックを抜けるまでです。
ブロックを抜けると変数は消滅するので、一時的に大きなメモリを使用する処理をブロックに分けることでメモリの使用量を抑えることもできます。
#include <stdio.h>
int main()
{
{
int num = 0;
}
{
//上の変数numとは無関係
int num = 1;
}
}
for文などのブロック
ブロックによって有効範囲が区切られるのはfor文やif文などでも同様です。
#include <stdio.h>
int main()
{
//この変数xはfor文内でのみ有効
for (int x = 0; x < 10; x++)
{
printf("%d", x);
}
printf("\n");
//これはNG
//printf("%d", x);
int y;
for (y = 0; y < 10; y++)
{
printf("%d", y);
}
printf("\n");
//これはOK
printf("%d", y);
if (1) {
//この変数numはif文を抜けるまで有効
int num = 0;
}
}
13行目ではfor文の初期化式で宣言された変数xにアクセスしようとしていますが、これはエラーとなります。
for文の初期化式で宣言された変数は、ループブロック内で宣言されたのと同じ扱いとなり、ループの外側からはアクセスできない変数となります。
ループ処理後にもループカウンタにアクセスしたい場合は、あらかじめfor文の外側で変数を宣言する必要があります。
グローバル変数
グローバル変数は、そのプログラム内のどこからでもアクセスが可能な変数です。
#include <stdio.h>
int g_global = 0;
void Inc()
{
g_global++;
}
int main()
{
g_global = 5;
Inc();
printf("%d", g_global); //6
getchar();
}
このグローバル変数g_global
は、main関数からも自作関数からもアクセスが可能な変数になります。
グローバル変数は一見便利に見えますが、使用はあまり推奨されません。
どこからでもアクセス可能なのは利点なのですが、欠点でもあるのです。
コードの規模が大きくなると、どこでグローバル変数の値を書き換えたかを把握するのが困難となり、バグの発生率が上がります。
値のやり取りは関数の引数や戻り値で行うのを基本とし、グローバル変数はできるだけ使用を避けましょう。
グローバル変数を使用したほうがコードがわかりやすくなる場合や、プログラムの実行効率のためにやむを得ない場合などに限って使用すべきです。
なお、値を書き換える必要がない場合は定数やマクロにすることができます。
詳しくは定数とマクロの項で説明します。
グローバル変数の寿命
グローバル変数はプログラムの起動時に作成され、プログラムの終了時まで存在します。
グローバル変数の初期化
グローバル変数を初期化しない場合は自動的に0で初期化されます。
#include <stdio.h>
//グローバル変数
//初期化しないと自動的に0
int g_global;
int main()
{
//ローカル変数
//初期化しないと値は不定
int local;
printf("%d", g_global); //0
printf("%d", local); //何が表示されるかわからない
getchar();
}
グローバル変数の初期化は、プログラムの開始時に一度だけ行われます。
グローバル変数の初期化に使用する値は定数でなければなりません。
変数や関数の戻り値などを初期化値に使用することはできません。
#include <stdio.h>
int Func()
{
return 1;
}
//関数の戻り値で初期化はできない
int g_global = Func();
int main() {}
C++ではグローバル変数の初期化に関数の戻り値などを使用することができます。
Visual StudioなどのC/C++コンパイラではエラーになりません。
Visual Studioでは、ソースファイル名の拡張子を「.c」にするとC++の機能は使用できなくなり、C言語としてコンパイルされます。
その場合はグローバル変数の初期化は定数でなければエラーとなります。
(Visual Studioではソースファイル名を特に変更していない場合、ファイル名は「Source.cpp」になっています。)