閉じる

C言語のスコープとは?変数の有効範囲を基礎から解説

C言語では、変数は「どこで宣言したか」によって見える範囲が決まります。

これをスコープと言います。

本記事では、ブロック、ファイル、関数パラメータの3種類のスコープを基礎から整理し、動くサンプルで確認しながら、初心者がつまずきやすい宣言位置やシャドーイングの注意点まで丁寧に解説します。

C言語のスコープの基本(変数の有効範囲)

スコープとは何か

スコープとは、識別子(変数名や関数名など)が「見える」ソースコード上の範囲のことです。

C言語は静的スコープ(レキシカルスコープ)ですので、どの識別子が参照されるかはプログラムのテキスト上の位置で決まり、実行時の流れでは変わりません。

もっとも内側のブロックで宣言された同名の変数が優先されるのが基本ルールです。

スコープとライフタイムの違い

スコープ(見える範囲)とライフタイム(記憶域期間、変数が存在する期間)は別物です。

ブロックの変数はブロック内でしか見えず、ブロックを抜けると破棄されます。

一方、ファイルスコープの変数はプログラムの開始から終了まで存在する静的記憶域期間を持ちます。

見えない場所にあっても、存在自体は続く場合があります。

以下に初学者が把握しやすいよう、スコープとライフタイムの関係を整理します。

種類スコープ(見える範囲)ライフタイム(記憶域期間)典型例
ブロックスコープ宣言したブロック内のみブロックを抜けるまで(自動記憶域期間)関数内の int i;
ファイルスコープその宣言位置から同一Cファイル末尾までプログラム開始から終了まで(静的記憶域期間)関数の外の int g;
関数パラメータ対応する関数本体の中だけ関数の実行中(自動記憶域期間)int f(int n)n

なお、本記事はスコープに焦点を当てます。

ライフタイムの詳説や関連するテクニックは扱いません。

C言語のスコープの種類

ブロックスコープ(ローカル変数)

波かっこで囲まれたブロック(関数本体、ifforwhile、任意の { ... } など)の中で宣言された変数は、そのブロックの中からのみ参照できます。

ブロック終了とともに見えなくなり、通常は変数自体も破棄されます。

内側のブロックで同名の変数を宣言すると、外側の同名変数を一時的に隠します(シャドーイング)。

ファイルスコープ(ファイル全体で有効)

いずれのブロックにも属さない場所(関数の外側)で宣言した識別子はファイルスコープを持ちます。

同じCファイル内では、その「宣言位置からファイル末尾まで」で見えます。

別の関数からも参照できますが、宣言より前の位置では見えない点に注意が必要です(宣言位置が効きます)。

関数パラメータのスコープ

関数の仮引数(パラメータ)は、その関数本体の中でのみ有効です。

関数の外からは参照できませんし、同じ関数の中でパラメータ名を再宣言することもできません。

パラメータは一般のローカル変数と同様、ブロックスコープを持つ識別子として扱われます。

コード例で学ぶC言語のスコープ

if/for/while内の変数の有効範囲

ブロック内で宣言した変数はブロック外から使えません。

for の初期化子で宣言した変数も、その for の中だけが有効範囲です。

C言語
#include <stdio.h>

int main(void) {
    printf("case A: for ループで i を宣言\n");
    for (int i = 0; i < 3; i++) {
        printf("  i = %d\n", i);
    }
    // printf("%d\n", i); // ← コンパイルエラー: i は for の外では未定義

    printf("case B: ループ前に j を宣言\n");
    int j = 0; // この j は main のブロック全体で有効
    for (; j < 3; j++) {
        printf("  j = %d\n", j);
    }
    printf("ループ後の j = %d\n", j); // ここでは j を使えます (j は 3)

    printf("case C: if のブロック\n");
    if (1) {
        int k = 42; // k は if ブロック内のみ有効
        printf("  k = %d\n", k);
    }
    // printf("%d\n", k); // ← コンパイルエラー: k は if の外では未定義

    return 0;
}
実行結果
case A: for ループで i を宣言
  i = 0
  i = 1
  i = 2
case B: ループ前に j を宣言
  j = 0
  j = 1
  j = 2
ループ後の j = 3
case C: if のブロック
  k = 42

このように、どこで宣言したかがその変数をどこまで使えるかを決めます。

ネストとシャドーイングの注意点

内側のブロックで同名の変数を宣言すると外側の同名変数を隠します(シャドーイング)。

関数パラメータも同様に、関数内では外側(ファイルスコープ)の同名変数を隠します。

C言語
#include <stdio.h>

int value = 100; // ファイルスコープ: この宣言位置からファイル末尾まで見える

void show_and_shadow(int value) {
    // ↑ この関数パラメータ value はファイルスコープの value を隠す
    printf("show_and_shadow 内(引数 value): %d\n", value);

    {
        // さらに内側のブロックで同名のローカル変数を宣言して引数を隠す
        int value = 1; // ここではこの value が最優先
        printf("show_and_shadow の内側ブロック(ローカル value): %d\n", value);
    }

    // 内側ブロックを抜けると、再び関数パラメータの value に戻る
    printf("show_and_shadow 内(引数 value に戻る): %d\n", value);
}

int main(void) {
    printf("main から見える file-scope value: %d\n", value);

    // 関数引数の value がファイルスコープの value を隠す
    show_and_shadow(200);

    // 関数を抜けてもファイルスコープの value はそのまま
    printf("main から再び file-scope value: %d\n", value);

    // シャドーイングの別例: 同じブロック内での再宣言は不可
    int x = 10;
    printf("外側の x = %d\n", x);
    {
        int x = 20; // 外側の x を隠す(内側ブロックではこの x が有効)
        printf("内側ブロックの x = %d\n", x);
    }
    printf("外側に戻った後の x = %d\n", x);

    return 0;
}
実行結果
main から見える file-scope value: 100
show_and_shadow 内(引数 value): 200
show_and_shadow の内側ブロック(ローカル value): 1
show_and_shadow 内(引数 value に戻る): 200
main から再び file-scope value: 100
外側の x = 10
内側ブロックの x = 20
外側に戻った後の x = 10

内側で宣言した同名変数は、そのブロックの中だけで外側を隠します。

ブロックを出れば外側の変数が再び見えるようになります。

宣言位置でスコープが決まる

識別子は「宣言された位置から」有効になります。

ファイルスコープの変数も、宣言より前の位置では見えません。

まずは悪い例(コンパイルエラー)を見てから、正しい配置を確認します。

悪い例(コンパイルエラーになるコード)

C言語
#include <stdio.h>

void print_g(void) {
    printf("g = %d\n", g); // エラー: ここでは g がまだ宣言されていない
}

int g = 10; // この位置から末尾までは見えるが、print_g より後にある

int main(void) {
    print_g();
    return 0;
}
実行結果
(コンパイルエラーの例)
error: ‘g’ undeclared (first use in this function)

良い例(宣言を前に出す)

C言語
#include <stdio.h>

int g = 10; // 先に宣言・定義しておく(この位置からファイル末尾まで有効)

void print_g(void) {
    printf("g = %d\n", g); // OK: g は見える
}

int main(void) {
    print_g();
    return 0;
}
実行結果
g = 10

同じことはブロックスコープでも成り立ちます。

変数はそのブロックの中でも「宣言より前」では使えません。

C99以降はブロックの途中で宣言できますが、必ず使用より前に置く必要があります。

初心者向けC言語スコープのコツ

変数は最小スコープで宣言

変数は必要になる直前の、できるだけ小さいブロックで宣言すると読みやすく安全です。

たとえば、ループ専用のカウンタなら for (int i = 0; ...) のように for の初期化子で宣言します。

こうすることで、他の箇所から誤って参照・変更される可能性を減らせます。

C99以降であればブロックの途中で宣言できるため、「使う直前で宣言」を基本にすると良いです。

名前の衝突を避けるポイント

同名の変数が異なるスコープに存在すると、シャドーイングが発生して意図しない値を扱ってしまうことがあります。

特に外側の変数を内側で隠してしまうとバグの温床になります。

紛らわしい汎用名(例: data, value, tmp)を多用せず、用途を表す名前にし、内側のブロックではできるだけ別名を選ぶのが無難です。

必要以上にスコープを広げないことも衝突回避に有効です。

よくあるミスと対処(スコープ)

初心者が陥りやすいスコープのミスとしては、以下が代表的です。

  • ループ変数をループの外で使おうとしてコンパイルエラーになる。対処として、外でも使いたい場合はループの外側で変数を宣言してからループで使用します。
  • 変数の宣言より前にその変数を参照してしまう。対処として、宣言を使用より前に移動します。ファイルスコープの識別子も「宣言位置から有効」なので順序に注意します。
  • 内側のブロックで外側と同名の変数を宣言してしまい、意図せずシャドーイングが発生する。対処として、同名を避けるか、ブロックを分けずに同じ変数を使い回す設計にします。

スコープの基本原則はシンプルです。

「どこで宣言したか」を常に意識し、「外側と内側で同名を持たない」「宣言は使用の直前で」という2点を守るだけで、多くのトラブルを回避できます。

まとめ

C言語のスコープは、変数名やパラメータが「どこから見えるか」を決める、非常に重要な基礎概念です。

ブロックスコープはブロック内だけ、ファイルスコープは宣言位置からファイル末尾まで、関数パラメータは関数本体の中だけで有効という原則を押さえましょう。

あわせて、スコープ(見える範囲)とライフタイム(存在期間)は別であることも忘れてはいけません。

実用上は、変数を最小スコープで宣言し、同名の衝突を避けるだけで、読みやすく安全なコードになります。

ここで扱ったサンプルを手元で動かし、「宣言位置」と「見える範囲」の関係を体感することが、スコープ理解の近道です。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!