C言語

変数が使用できる有効な範囲(グローバル変数とローカル変数とスコープ)

今まで学んで使用してきた変数ですが、C言語では、変数宣言する場所により、使用できる範囲が決められています。今回は、変数宣言する場所と変数の有効範囲について学びたいと思います。

関数内で宣言した変数

今まで使用してきた変数は、すべて関数の中で変数宣言してきました。この関数内で宣言された変数や、関数の仮引数の変数をローカル変数といいます。
このローカル変数は、変数宣言した関数内でしか使用できません。関数を呼び出したとき、変数宣言を行うことにより、メモリに変数を割り当てて、関数が終了すると、メモリに割り当てた変数は削除されて使用できなくなります。このように変数が有効になる範囲のことをスコープといいます。
ローカル変数は、関数が終了すると削除されるため、別の関数で同じ名前の変数が宣言されても、それは、別の変数となります。
また、関数が終了する前に、別の関数を呼び出した場合、呼び出した関数内では、呼び出し元の関数のローカル変数は使用できません。呼び出した関数が実行されているとき、呼び出し元の関数のローカル変数は、削除されているわけではありませんが、スタック領域と呼ばれる場所に退避されているためです。スタック領域については、どこかのタイミングで紹介できればと思っています。

ローカル変数のスコープを確認するためのサンプルプログラムを次に示します。

#include <stdio.h>

void update(void);

int main(void)
{
    int number = 5;

    update();
    printf("%d\n", number);
    update();
    printf("%d\n", number);

    return 0;
}

void update(void)
{
    int number;

    number++;
    printf("%d\n", number);
}

私の環境では、実行結果は次のようになりました。

実行結果

1
5
1
5

main関数では、number変数を5で初期化しています。
この後、update関数を呼び出し、number変数をインクリメントしますが、main関数のnumber変数とは別の変数のため、出力は、5とはなりません。
update関数では、number変数を初期化しないで、インクリメントしています。ローカル変数は、初期化しないで変数宣言すると、不定値になり、その値にインクリメントすることになります。私の環境では、インクリメントした結果を表示すると 1 が出力されました。
そして、main関数に戻った後の出力では、main関数のnumber変数の 5 が出力されます。
次update関数では、前のupdate関数でインクリメントした変数は、関数が終了した時点でメモリから削除されているため、新たに変数宣言しています。そして、前のupdate関数と同様に、不定値をインクリメントすることになります。
最後に、main関数のnumber変数の5が出力されます。

このように、同じ変数であっても、別物ということが分かったかと思います。また、関数が終了すると、変数は削除され、次、同じ関数を呼び出しても、前に呼び出した情報は無くなっていることもわかったかと思います。

ローカル変数とブロックの関係

ローカル変数は、関数内でしか使えず、関数が終了すると使用できなくなります。ただし、関数内ならどこでも使えるかというと、使えないケースもあります。
それは、ローカル変数の有効範囲は、正確には、関数内ではなく、ブロックで囲まれている範囲が有効範囲だからです。通常、ローカル変数は、関数の先頭で変数宣言を行います。そのため、この場合は、関数内ならどこでも使えます。ただし、変数宣言は、ブロックの先頭であれば、どこでも変数宣言できます。ブロックは、関数内で、ブロックのみを使うことも可能です。なので、関数の先頭ではなく、関数内で、関数のブロックとは別のブロック内で変数宣言した場合は、そのブロック内でのみ変数が有効になります。同じブロック内に同じ名前の変数を宣言することはできませんが、ブロックが異なれば、同じ名前の変数でも宣言することは可能です。

関数内でブロックを記述し、そのブロック内で変数宣言した場合のサンプルプログラムを示します。

#include <stdio.h>

int main(void)
{
    int number1 = 1;
    int number2 = 2;
    {
        int number1;
        number1 = 10;
        printf("%d\n", number1);
        printf("%d\n", number2);
    }
    printf("%d\n", number1);
    printf("%d\n", number2);
    return 0;
}

7行目にブロックだけを記述しています。このため、7~12行目のブロック内で変数宣言したnumber1変数は、このブロック内が有効範囲になります。main関数の始めに変数宣言したnumber1変数は、main関数のすべてのブロックで有効ですが、同じ名前の変数を別のブロック内の先頭で宣言してもエラーにはなりません。この場合、処理の実行されているブロックの変数が優先されるためです。
実行結果は、次のようになります。

実行結果

10
2
1
2

10行目のprintf関数では、同じブロック内で代入した10が出力されているのに対し、ブロックを抜けた13行目では、10ではなく、初期化した値の1が代入されていることがわかると思います。
そして、number2変数は、どのブロックでも、最初に初期化した値が出力されていることがわかると思います。
このように、変数宣言したブロックにより、有効範囲が違います。また、変数宣言したブロック内で、ブロックが記述されていても、そのブロックに対しても有効となります。

古いコンパイラなどによって、関数の先頭以外のブロック内の先頭で変数宣言してもコンパイルエラーとなる場合があります。

関数外で宣言した変数

今まで、変数宣言は、関数内で行ってきました。これは必ず関数内で行わなければならないわけではありません。関数の外で変数宣言することも可能です。
このように関数の外で変数宣言した変数をグローバル変数といいます。
グローバル変数は、変数宣言したソースファイル内のすべての関数から使用することが可能です。そして、プログラムの開始から終了するまで、変数の値は保持されます。
そして、初期化しないで変数宣言した場合は、0 が代入されます。
グローバル変数を使用したサンプルプログラムを次に示します。

#include <stdio.h>

int number;     /* グローバル変数 */

void update(void);

int main(void)
{
    number = 5;
    update();
    printf("%d\n", number);
    number++;
    update();
    printf("%d\n", number);

    return 0;
}

void update(void)
{
    number++;
    printf("%d\n", number);
}

3行目でグローバル変数の変数宣言をしています。このため、main関数でも、update関数でも共通して、number変数が使用できるようになります。
main関数では、numberに5を代入してから、update関数を呼び出しています。update関数では、number変数をインクリメントしていますが、number変数はソースファイル内のどの関数でも使用可能ですので、インクリメントすると、6 となります。
その後、update関数を終了して、main関数に戻り、printf関数で、numberの値を出力しますが、update関数が終了しても、number変数の値は保持されたままとまります。
このように、グローバル変数は、関数の呼び出し先でも、呼び出し元でも、同じファイル内の関数の場合は、共通的に使用することが可能です。
サンプルプログラムの実行結果は、次のようになります。

実行結果

6
6
8
8

次に、グローバル変数とローカル変数に同じ変数名を宣言した場合です。
この場合、エラーにはならず、ローカル変数が優先されるようになります。これは、同じローカル変数名を、さらにブロックで囲われた中で宣言した場合と同様です。
グローバル変数とローカル変数が同じ変数名となっているサンプルプログラムを次に示します。

#include <stdio.h>

int number;     /* グローバル変数 */

void update(void);

int main(void)
{
    int number;

    number = 5;
    update();
    printf("%d\n", number);
    number++;
    update();
    printf("%d\n", number);

    return 0;
}

void update(void)
{
    number++;
    printf("%d\n", number);
}

前のサンプルプログラムから、main関数にnumberの変数宣言を追加したのみのサンプルプログラムになります。
この場合、main関数で使用するnumber変数は、ローカル変数になります。update関数は、グローバル変数が使用されます。
実行結果は次のようになります。

実行結果

1
5
2
6

実行結果からもわかるかもしれませんが、main関数のnumberは、最初に5が代入され、その値にインクリメントしています。
一方、update関数は、グローバル変数は、初期化されていないため、宣言時、暗黙的に 0 が代入されます。update関数が呼ばれるたびに、グローバル変数をインクリメントしています。
このため、結果は、上記のようになっています。

グローバル変数とソースファイルの関係

グローバル変数は、同じソースファイル内のすべての関数で使用できますが、別のファイルの関数から使用できません。
しかし、「ソースコードを複数のファイルに分割」で学んだように、ヘッダーファイルに、extern宣言を付けて、変数宣言することにより、そのヘッダーファイルをインクルードすることで、別のファイルの関数からでも使用できるようになります。

以上が、変数が使用できる有効範囲として、ローカル変数とグローバル変数についてでした。
変数は、ローカル変数とグローバル変数の他に、スタティック変数と呼ばれているものがあります。
次は、このスタティック変数についてと、スタティック変数の有効範囲について学びたいと思います。

変数が使用できる有効な範囲(スタティック変数とスコープ)

-C言語