変数のスコープ

変数の有効範囲の違い

変数は、その宣言を書く場所によって有効範囲が異なります。
これを変数のスコープといいます。

変数のスコープ外からは、その変数を参照することはできなくなります。

#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
    }

    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は今まで使ってきた普通の変数です。
main関数内で宣言した変数は、main関数内でのみ使用可能です。
これをローカル変数と言います。

10~17行目は波括弧{}でブロックが作られています。
ブロック内で宣言された変数は、そのブロック内でのみ有効なローカル変数となります。
12行目の変数local2は、17行目のブロックを抜けた時点で消滅します。

14、15、16行目ではグローバル変数と、それぞれのローカル変数の値を表示しています。
内側のスコープからは、その外側のスコープにある変数を参照することができます。

反対に、外側のスコープからは内側のスコープの変数は見えなくなります。
そのため、19、20行目では変数local2の値を取り出すことはできません。

ローカル変数は、有効範囲内の処理を抜けるとメモリ上から消滅します。
そのため変数のスコープは変数の寿命、とも呼ばれます。

これは関数の場合も同様です。

#include <stdio.h>

int Test()
{
	int num1 = 3;
	int num2 = 5;

	return 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); //2

	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」のままです。

ひとまとめの処理ごとにブロックでコードを分割することで、同名の変数であってもそれぞれ独立して使用することができます。
ブロックで分割すると、ブロック内で宣言した変数の寿命はそのブロックを抜けるまでです。
ブロックを抜けると変数は消滅しますので、メモリの使用量を抑える効果もあります。

for文などのブロック

ブロックによって有効範囲が区切られるのはfor文などでも同様です。

#include <stdio.h>

int main()
{
    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);

    getchar();
}

12行目では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();
}

グローバル変数は一見便利に見えますが、使用はあまり推奨されません。
どこからでも値を書き換えられるというのは利点なのですが、欠点でもあるのです。

コードの規模が大きくなると、どこでグローバル変数の値を書き換えたかを把握するのが困難となり、バグの発生率が上がります。
グローバル変数はできるだけ使用を避け、使用したほうがわかりやすい場合やプログラムの実行効率が良い場合などに限り使用しましょう。

グローバル変数の初期化

ローカル変数を初期化しない場合は値は不定です。
グローバル変数を初期化しない場合は自動的に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()
{
    int num;
    //何か処理...
    return num;
}

//関数の戻り値で初期化は×
int g_global = Func();

int main()
{
    //省略
}

C++ではグローバル変数の初期化に関数の戻り値などを使用することができます。
Visual StudioなどのC/C++コンパイラではエラーになりません。

Visual Studioでは、ソースファイル名の拡張子を「.c」にするとC言語としてコンパイルされます。
その場合はグローバル変数の初期化は定数でなければエラーとなります。
(Visual Studioではソースファイル名を特に変更していない場合、ファイル名は「Source.cpp」になっています。)