閉じる

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

C言語では、変数がどこで使えるか(可視性)はプログラムの正しさに直結します。

本記事では、スコープ(有効範囲)をブロックとファイルの観点から丁寧に整理し、宣言位置による可視性の違いライフタイム(生存期間)との違いも基礎から解説します。

実行可能なサンプルとエラー例を通して、初心者の方でも段階的に理解できるようにします。

C言語のスコープの基礎

スコープとは(変数の有効範囲)

スコープとは、ある名前(変数や関数名)が参照できる範囲のことです。

C言語では、中括弧で囲まれたブロックファイル全体など、宣言の位置によって参照可能な範囲が決まります。

たとえば関数の中で宣言した変数は、その関数内のブロックでのみ有効で、関数の外からは見えません。

宣言位置で決まるスコープと可視性

C言語の名前解決は「宣言の位置から先(後方)」に向けて行われます。

つまり、同じブロック内では宣言より前では使えません

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

これにより、宣言位置が1行違うだけで意味が変わることがありますので、読みやすい位置で宣言する習慣が重要です。

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

スコープは「名前が見える範囲」であり、ライフタイム(記憶域期間)は「オブジェクトが存在する期間」です。

関数内の通常のローカル変数は自動記憶域期間を持ち、ブロックの開始で生成され、ブロックの終わりで破棄されます。

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

スコープが広いからといって、必ずしもどこからでも好きに使って良いわけではありません

見通しや安全性を考え、必要最小限の範囲に留めるのが基本です。

以下に、スコープとライフタイムの関係を比較して整理します。

種類典型的な宣言場所スコープ(可視性)ライフタイム(記憶域期間)
ブロックスコープ関数内のブロック({})宣言位置からそのブロックの終わりまで自動(ブロック入場〜退場)
関数パラメータ関数の仮引数関数本体内自動(関数呼び出し中)
ファイルスコープ関数の外(グローバル)宣言位置から翻訳単位末まで静的(プログラム開始〜終了)

ブロックスコープ(ローカル変数の有効範囲)

{}で囲まれたブロックのスコープ

ブロックとは{ ... }で囲まれた範囲です。

ブロック内で宣言した変数は、そのブロック内でのみ有効です。

内側のブロックで同名の変数を宣言すると、外側の変数が隠されます。

C言語
// ブロックスコープの基本とシャドーイングの例
#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で値を準備したい場合は、手前のブロックで宣言します。

C言語
// 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で初期値が必要なら、次のようにブロックを使ってスコープを限定します。

C言語
#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

関数の引数とローカル変数のスコープ

関数の仮引数と関数内で宣言したローカル変数は、その関数の本体でのみ有効です。

呼び出し元の変数名と同名でも、別物として扱われます。

C言語
// 関数パラメータとローカル変数のスコープ
#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

ネストとシャドーイング(同名変数の優先順位)

内側のブロックで外側と同名の変数を宣言すると、内側の変数が優先されます。

宣言の位置によって見える対象が変わる点にも注意してください。

C言語
// 宣言位置による見え方の変化(ファイルスコープとローカルのシャドーイング)
#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ファイルとそれに含まれるヘッダ)の末尾まで有効です。

宣言より前の位置では見えません。

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

一方、宣言より前で使うとエラーになります。

C言語
// エラー例: 宣言前に 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はその関数の残りの範囲では隠されます。

C言語
#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は「宣言位置から先」のみ可視です。

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

対策としては、必要な直前で宣言し、宣言と初期化をセットにするのが安全です。

C言語
// 対策: 宣言と初期化を使用直前で行う
#include <stdio.h>

int main(void) {
    int x = 3;
    printf("%d\n", x);  // OK
    return 0;
}
実行結果
3

ブロック外参照のコンパイルエラー

ブロック内でしか見えない変数を、ブロックの外から参照するとエラーになります。

C言語
// エラー例: ブロック外参照
#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'?

対策は、必要な最小範囲で宣言し、必要な範囲外には持ち出さないことです。

どうしても外側で使う必要があるなら、外側のブロックで宣言し、内側では値だけ更新するようにします。

C言語
// 対策: 外側で宣言し、内側で値を更新
#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_totalcount_localなど、文脈が伝わる接頭辞・接尾辞)
  • ループ変数はできるだけループのスコープに閉じ込める(C99以降のfor (int i = ...)を活用)

「とりあえず上のスコープに置く」は将来のバグの原因になります。

必要性と可読性を基準に、変数の位置と名前を決めましょう。

まとめ

本記事では、C言語におけるスコープ(可視性)を、ブロックとファイルという2つの観点から解説しました。

ポイントは次の通りです。

変数は宣言位置から先のスコープでのみ有効であり、内側のスコープは外側をシャドーイングします。

また、スコープ(見える範囲)とライフタイム(存在期間)は別の概念であることを区別しましょう。

実装では、スコープは可能な限り狭く、名前は衝突しないように設計することで、読みやすく安全なコードになります。

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

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

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

URLをコピーしました!