C言語では、変数がどこで使えるか(可視性)はプログラムの正しさに直結します。
本記事では、スコープ(有効範囲)をブロックとファイルの観点から丁寧に整理し、宣言位置による可視性の違いやライフタイム(生存期間)との違いも基礎から解説します。
実行可能なサンプルとエラー例を通して、初心者の方でも段階的に理解できるようにします。
C言語のスコープの基礎
スコープとは(変数の有効範囲)
スコープとは、ある名前(変数や関数名)が参照できる範囲のことです。
C言語では、中括弧で囲まれたブロックやファイル全体など、宣言の位置によって参照可能な範囲が決まります。
たとえば関数の中で宣言した変数は、その関数内のブロックでのみ有効で、関数の外からは見えません。
宣言位置で決まるスコープと可視性
C言語の名前解決は「宣言の位置から先(後方)」に向けて行われます。
つまり、同じブロック内では宣言より前では使えません。
また、内側のブロックで同名の変数を宣言すると、外側の同名変数をシャドーイング(隠蔽)します。
これにより、宣言位置が1行違うだけで意味が変わることがありますので、読みやすい位置で宣言する習慣が重要です。
スコープとライフタイムの違い
スコープは「名前が見える範囲」であり、ライフタイム(記憶域期間)は「オブジェクトが存在する期間」です。
関数内の通常のローカル変数は自動記憶域期間を持ち、ブロックの開始で生成され、ブロックの終わりで破棄されます。
一方、ファイルスコープの変数は静的記憶域期間を持ち、プログラム開始から終了まで存在します。
スコープが広いからといって、必ずしもどこからでも好きに使って良いわけではありません。
見通しや安全性を考え、必要最小限の範囲に留めるのが基本です。
以下に、スコープとライフタイムの関係を比較して整理します。
種類 | 典型的な宣言場所 | スコープ(可視性) | ライフタイム(記憶域期間) |
---|---|---|---|
ブロックスコープ | 関数内のブロック({} ) | 宣言位置からそのブロックの終わりまで | 自動(ブロック入場〜退場) |
関数パラメータ | 関数の仮引数 | 関数本体内 | 自動(関数呼び出し中) |
ファイルスコープ | 関数の外(グローバル) | 宣言位置から翻訳単位末まで | 静的(プログラム開始〜終了) |
ブロックスコープ(ローカル変数の有効範囲)
{}で囲まれたブロックのスコープ
ブロックとは{ ... }
で囲まれた範囲です。
ブロック内で宣言した変数は、そのブロック内でのみ有効です。
内側のブロックで同名の変数を宣言すると、外側の変数が隠されます。
// ブロックスコープの基本とシャドーイングの例
#include <stdio.h>
int main(void) {
int a = 10; // 外側ブロックの a
printf("outer a = %d\n", a);
{ // 内側ブロックの開始
int a = 20; // 外側の a をシャドーイング
int b = 30; // b はこのブロック内だけ有効
printf("inner a = %d, b = %d\n", a, b);
} // 内側ブロックの終わり(ここで b は破棄)
printf("after block, a = %d\n", a);
// printf("%d\n", b); // エラー: b はこの位置からは見えない
return 0;
}
outer a = 10
inner a = 20, b = 30
after block, a = 10
このように、内側で宣言した変数は外側から参照できません。
一方で、内側に同名の変数を作ると外側の同名変数は隠され、内側の変数が優先されます。
if/for/whileの初期化子のスコープ
C言語ではfor文の初期化子に限り、ブロックスコープの変数を宣言できます(C99以降)。
if文やwhile文の丸括弧内には初期化子を置けません(C++とは異なります)。
ifやwhileで値を準備したい場合は、手前のブロックで宣言します。
// C99以降: for の初期化子で宣言した i は for 文の中だけ有効
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) { // i は for 文のスコープ内だけ見える
printf("%d ", i);
}
printf("\n");
// printf("%d\n", i); // エラー: i はここでは見えない
return 0;
}
0 1 2
if/whileで初期値が必要なら、次のようにブロックを使ってスコープを限定します。
#include <stdio.h>
int main(void) {
{ // スコープを限定するためのブロック
int count = 0; // このブロック内でのみ有効
while (count < 3) {
printf("%d ", count);
count++;
}
} // ここで count は破棄される
// printf("%d\n", count); // エラー: count は見えない
return 0;
}
0 1 2
関数の引数とローカル変数のスコープ
関数の仮引数と関数内で宣言したローカル変数は、その関数の本体でのみ有効です。
呼び出し元の変数名と同名でも、別物として扱われます。
// 関数パラメータとローカル変数のスコープ
#include <stdio.h>
void show_sum(int a, int b) { // a, b は show_sum 内でのみ有効
int sum = a + b; // sum も関数内だけ有効
printf("sum = %d\n", sum);
}
int main(void) {
int a = 5, b = 7; // これは main の a, b
show_sum(a, b); // 値が渡されるが、別の領域
printf("main: a = %d, b = %d\n", a, b);
return 0;
}
sum = 12
main: a = 5, b = 7
ネストとシャドーイング(同名変数の優先順位)
内側のブロックで外側と同名の変数を宣言すると、内側の変数が優先されます。
宣言の位置によって見える対象が変わる点にも注意してください。
// 宣言位置による見え方の変化(ファイルスコープとローカルのシャドーイング)
#include <stdio.h>
int value = 100; // ファイルスコープ
void demo(void) {
printf("before local: value = %d\n", value); // 100 が見える
int value = 42; // ここから先はローカルの value が優先
printf("after local: value = %d\n", value); // 42 が見える
}
int main(void) {
demo();
return 0;
}
before local: value = 100
after local: value = 42
シャドーイングはバグの温床になりがちです。
紛らわしい同名宣言は避け、役割が一目で分かる名前を使うのが安全です。
ファイルスコープ(関数の外で宣言した変数の有効範囲)
宣言位置から翻訳単位末までが有効範囲
関数の外で宣言された変数(いわゆるグローバル変数)は、宣言位置から同じ翻訳単位(通常は1つの.cファイルとそれに含まれるヘッダ)の末尾まで有効です。
宣言より前の位置では見えません。
// ファイルスコープの基本: 宣言位置より後ろで使える
#include <stdio.h>
void use_g(void); // 関数プロトタイプ
int g = 10; // ファイルスコープ: ここから下で見える
void use_g(void) {
printf("g = %d\n", g);
}
int main(void) {
use_g();
return 0;
}
g = 10
一方、宣言より前で使うとエラーになります。
// エラー例: 宣言前に g を使用
#include <stdio.h>
int main(void) {
printf("%d\n", g); // ここでは g はまだ宣言されていない
return 0;
}
int g = 5; // ここに到達するまで g は不可視
main.c: In function 'int main()':
main.c:5:20: error: 'g' was not declared in this scope
(補足) どうしても前方で参照したい場合はextern
による前方宣言が必要ですが、複数ファイル設計やリンケージの話題になるため本記事では深入りしません。
ローカル変数とファイルスコープの衝突時の扱い
ローカル変数とファイルスコープの変数が同名の場合、ローカルが優先されます。
次の例では、関数内でcount
を宣言した瞬間から、ファイルスコープのcount
はその関数の残りの範囲では隠されます。
#include <stdio.h>
int count = 1000; // ファイルスコープ
void report(void) {
printf("before local: count = %d\n", count); // 1000
int count = 3; // ここから先はローカルが優先
printf("after local: count = %d\n", count); // 3
}
int main(void) {
report();
printf("global in main: count = %d\n", count); // 1000 (関数内でローカルを宣言していないため)
return 0;
}
before local: count = 1000
after local: count = 3
global in main: count = 1000
同名は混乱を招きやすいため、意図が明確にならない限り避けることをおすすめします。
スコープで起きやすいミスと対策
宣言前に変数を使ってしまうエラー
同じブロック内で、宣言より前に変数名を使うとコンパイルエラーになります。
Cは「宣言位置から先」のみ可視です。
// エラー例: 宣言前の使用
#include <stdio.h>
int main(void) {
printf("%d\n", x); // エラー: x はまだ宣言されていない
int x = 3;
return 0;
}
main.c: In function 'int main()':
main.c:5:20: error: 'x' was not declared in this scope
対策としては、必要な直前で宣言し、宣言と初期化をセットにするのが安全です。
// 対策: 宣言と初期化を使用直前で行う
#include <stdio.h>
int main(void) {
int x = 3;
printf("%d\n", x); // OK
return 0;
}
3
ブロック外参照のコンパイルエラー
ブロック内でしか見えない変数を、ブロックの外から参照するとエラーになります。
// エラー例: ブロック外参照
#include <stdio.h>
int main(void) {
{
int tmp = 42; // tmp はこのブロック内だけ
printf("%d\n", tmp);
}
printf("%d\n", tmp); // エラー: tmp は見えない
return 0;
}
main.c: In function 'int main()':
main.c:9:20: error: 'tmp' was not declared in this scope; did you mean 'rmtmp'?
対策は、必要な最小範囲で宣言し、必要な範囲外には持ち出さないことです。
どうしても外側で使う必要があるなら、外側のブロックで宣言し、内側では値だけ更新するようにします。
// 対策: 外側で宣言し、内側で値を更新
#include <stdio.h>
int main(void) {
int tmp = 0; // 外側で宣言
{
tmp = 42; // 内側で更新
printf("inner: %d\n", tmp);
}
printf("outer: %d\n", tmp); // OK
return 0;
}
inner: 42
outer: 42
スコープを意識した変数名の付け方
スコープを踏まえた命名は、読みやすさと安全性を高めます。
次の指針が役立ちます。
- スコープを狭く保つ(必要な最小のブロックで宣言し、寿命を短くする)
- 同名の再宣言(シャドーイング)を避ける(紛らわしい名前はつけない)
- 役割が一目で分かる名前にする(例:
count_total
とcount_local
など、文脈が伝わる接頭辞・接尾辞) - ループ変数はできるだけループのスコープに閉じ込める(C99以降の
for (int i = ...)
を活用)
「とりあえず上のスコープに置く」は将来のバグの原因になります。
必要性と可読性を基準に、変数の位置と名前を決めましょう。
まとめ
本記事では、C言語におけるスコープ(可視性)を、ブロックとファイルという2つの観点から解説しました。
ポイントは次の通りです。
変数は宣言位置から先のスコープでのみ有効であり、内側のスコープは外側をシャドーイングします。
また、スコープ(見える範囲)とライフタイム(存在期間)は別の概念であることを区別しましょう。
実装では、スコープは可能な限り狭く、名前は衝突しないように設計することで、読みやすく安全なコードになります。