C言語基礎|識別子の有効範囲

C言語でプログラムが大きくなってくると、「この変数って今どれを指してるんだっけ?」って迷子になりがちです。
その迷子を防ぐための地図が 有効範囲(scope) で、さらに「今どこから見えるか?」という視点が 可視性(visibility) です。

同じ名前の変数を別の場所で宣言できてしまうのがC言語の特徴でもあるので、“同名が登場したときの優先ルール” を知っているかどうかで、バグの出やすさが大きく変わります。

有効範囲ってなに?

有効範囲はざっくり言うと、その名前(識別子)が通用する範囲です。

今回使う有効範囲の種類

種類ざっくり説明代表例
ファイル有効範囲ソースファイル全体(宣言位置から末尾まで)で通用関数の外で宣言した変数
ブロック有効範囲{ } の中だけで通用(宣言位置から対応する } まで)関数内の変数、forの中の変数

重要ルール:同じ名前が複数あると「内側が勝つ」

C言語では同名が存在できるので、衝突したときのルールが決まっています。

名前が衝突したときの優先順位

状況見える(優先される)隠れる
ファイル有効範囲 vs ブロック有効範囲ブロック有効範囲ファイル有効範囲
ブロック有効範囲(外側) vs ブロック有効範囲(内側)内側のブロック外側のブロック

この「隠れる」を シャドーイング(shadowing) と呼ぶことが多いです。
怖いのは、コンパイルエラーにならず普通に動くこと。だからこそルール理解が大事なんですよね。

サンプルプログラム

ファイル内・main内・for内で、同じ名前 price を3回宣言して、どれが使われるかを確認します。

識別子の有効範囲を確認する

プロジェクト名:chap6-20-1 ソースファイル名:chap6-20-1.c

// 識別子の有効範囲を確認する(price版)

#include <stdio.h>

int price = 120;   // A ファイル有効範囲

void print_price(void)
{
    printf("print_priceのprice = %d\n", price);
}

int main(void)
{
    int price = 999;   // B ブロック有効範囲(mainの中)

    print_price();     // ➀

    printf("mainのprice = %d\n", price);  // ➁

    for (int week = 0; week < 5; week++) {
        int price = week * 100;           // C ブロック有効範囲(forの中)
        printf("for内のprice = %d\n", price);  // ➂
    }

    printf("mainに戻ったprice = %d\n", price); // ➃

    return 0;
}

実行イメージ

print_priceのprice = 120
mainのprice = 999
for内のprice = 0
for内のprice = 100
for内のprice = 200
for内のprice = 300
for内のprice = 400
mainに戻ったprice = 999

どの price が使われているかを図で整理

ここが一番大事なので、図でスッと整理します。

3つの price の関係(見える範囲)

ファイル全体
  price = 120   ← A(ファイル有効範囲)

mainのブロック { --------------------------- }
  price = 999   ← B(Aを隠す)

  forのブロック { ------------------------- }
    price = week * 100  ← C(Bを隠す)
  }  ← forを抜けるとCは消える

} ← mainを抜けるとBも消える(Aだけが残る)

補足(図の読み方)

  • print_price は main の中ではなく、ファイルレベルにある関数
    → 関数内で見える price は A
  • main の中では B を宣言した瞬間から、A は見えなくなる
  • for の中で C を宣言した瞬間から、B は見えなくなる
  • for を抜けると C はスコープ外になるので、再び B が見える
  • main を抜けると B も消えるので、ファイル全体では A が残る

「宣言した直後から有効」ってどういうこと?

C言語では、識別子は 宣言を書き終えた直後から 有効になります。
ここが地味に落とし穴です。

例:これ、やりがち注意

int price = 120;

int main(void)
{
    int price = price;  // 右側のpriceは外側ではなく「今宣言したprice」
    printf("%d\n", price);
}

この右側の price は、「ファイルの price」ではなく、宣言中の price 自身を指します。
なので値は 120 にならず、不定値で初期化される可能性が高いです(危険!)。

登場する命令(書式と役割)

今回出てきた命令まとめ

命令書式何をする命令?
#include#include <stdio.h>printf など入出力関数の宣言を取り込む。
printfprintf(書式, …);書式付きで表示する。
forfor (初期化; 条件; 更新) 文回数や条件で繰り返す。
returnreturn 値;関数を終了して呼び出し元へ戻る(mainでは終了コードにもなる)
変数宣言型 名前 = 初期値;変数を作って初期化する(スコープの開始点になる)

使い分けのコツ(実務っぽい話)

同じ名前を内側で使い回せるのは便利ですが、実務では基本的にこう考えると安全です。

おすすめの方針

方針理由
内側で外側と同名を作らない。読んだ人が混乱しやすい・バグになりやすい。
どうしても同名にしたいなら範囲を短くする。for の中だけ、if の中だけなどに限定
役割が違うなら名前も変える。price と tmp_price など、意図が見える。