変数は、定義された場所によって、使える場所(通用範囲)が異なります。通用範囲のことを、スコープとも呼びます。
ローカル(局所)変数とは、関数の中で定義された変数のことです。ローカル変数のスコープは、その関数(正確にはブロック)の中になります。
int func(int x) { int sum = 0; for (int i=0; i<10; i++) { int y = x + i; sum = sum + y * y; } y, i の通用範囲ここまで // sum = sum + y * y; ←コンパイルエラーになる // return i; ←コンパイルエラーになる return sum; } sum, x の通用範囲ここまで int other(int y) { // return x; ←コンパイルエラーになる // return sum; ←コンパイルエラーになる return y + 5; }
sum という変数は、関数 func の中で定義してありますので、この関数の中でのみ使えます。関数 other では使えません。y という変数はもちろん関数 func の中で使えるのですが、さらに内側の for ループで定義されているので、for ループの中でのみ使えます。
関数の仮引数は、その関数の内側のブロックで定義されたものと解釈されます。したがって、関数 func の x は関数 other では使えません。for ループで定義された変数 i も、ループのブロックのみで用いることができます。
グローバル(大域)変数とは、関数の外で定義された変数のことです。関数をまたいで共通に使えます。
int a = 0; void add2(void) { a = a + 2; } void add5(void) { a = a + 5; } int main() { printf("%d\n", a); // 0 add2(); printf("%d\n", a); // 2 add5(); printf("%d\n", a); // 7 }
a という変数は関数の外側で定義されているので、main 関数でも使えますし、add2, add5 関数でも使えます。
グローバル変数は、一見したところ便利なように思えます。関数の引数を用いることなく、値の受け渡しができますが、大きな副作用を伴います。変数名は重複することができませんので、グローバル変数が増えてくると変数名が重複しないように命名するのが困難になります。そして動作不良を起こしたときに、プログラム上の疑うべき処理が飛躍的に増加し、デバッグが困難になります。スコープを狭くしておくことが、信頼性の高い、よいプログラムを書くコツです。
変数名は、同じブロックでは重複させることができません。
void func(void) { int a; double a; // コンパイルエラー }
スコープが異なると、変数名が重複してもよくなります。異なる変数として扱われます。
void func(void) { int x = 0; printf("%d", x); } // 0 void other(void) { int x = 5; printf("%d", x); } // 5
スコープが入れ子になっている場合は、内側のスコープの変数が優先されます。
int a = 5; // グローバル変数 int func(void) { printf("%d\n", a); // 5, グローバル変数 } int other(void) { int a = 10; // ローカル変数 printf("%d\n", a); // 10, ローカル変数 }
スコープの広いグローバル変数には、名前の重複を避けるために長い名前をつけるのが習慣です。スコープの狭いローカル変数(特にループ変数)には、重複の心配が少ないですし、頻出するので、短い名前をつけることが多いです。
グローバル変数を用いた、悪い例を示します。ループで用いる変数の定義を一度にすまそうと、グローバル変数にしてしまうと、思わぬ副作業が起こります。
int i; // 定義を一度ですませたい void func(void) { for (i=1; i<=3; i++) printf("%d ", i); } // 1 2 3 void other(void) { for (i=1; i<=3; i++) func(); } // 1 2 3 を3回繰り返す? int main() { other(); // 1 2 3 }
関数 other では3回ループを行い、関数 func を呼び出しているように見えます。しかし、func でも3回ループを行い、ループ制御には共通のループ変数 i を用いていますので、other のループに影響を与えてしまい、ループは即座に終了してしまうことでしょう。変数 i のスコープが広いため、関数同士に影響が出てしまいました。
以下のように改善すると、for ループごとに i を定義しなおしているので別の変数となり、どちらも正しく3回ループを行うようになります。変数 i のスコープが狭いおかげで、他の関数の影響を気にする必要がなくなります。
void func(void) { for (int i=1; i<=3; i++) printf("%d ", i); } // 1 2 3 void other(void) { for (int i=1; i<=3; i++) func(); } // 1 2 3 を3回繰り返す int main() { other(); // 1 2 3 1 2 3 1 2 3 }
ローカル変数であっても、スコープも狭くしておくとメリットがあります。
int main() { // int i; // ここで定義しているとエラーにならない for (int i=0; i<10; i++) { /* iはこのブロック専用 */ } for (int j=0; j<10; i++) { ... } // i は使えないので、コンパイルエラー ... }
2つめのループでは j++ を i++ とタイプミスしています。もしも変数 i をループ直前に定義していれば、この i++ がエラーにならず、j が変化せずに無限ループに陥ることでしょう。スコープを狭くすることで、このようなタイプミスをコンパイル時に検出できるようになります。