
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 など入出力関数の宣言を取り込む。 |
| printf | printf(書式, …); | 書式付きで表示する。 |
| for | for (初期化; 条件; 更新) 文 | 回数や条件で繰り返す。 |
| return | return 値; | 関数を終了して呼び出し元へ戻る(mainでは終了コードにもなる) |
| 変数宣言 | 型 名前 = 初期値; | 変数を作って初期化する(スコープの開始点になる) |
使い分けのコツ(実務っぽい話)
同じ名前を内側で使い回せるのは便利ですが、実務では基本的にこう考えると安全です。
おすすめの方針
| 方針 | 理由 |
|---|---|
| 内側で外側と同名を作らない。 | 読んだ人が混乱しやすい・バグになりやすい。 |
| どうしても同名にしたいなら範囲を短くする。 | for の中だけ、if の中だけなどに限定 |
| 役割が違うなら名前も変える。 | price と tmp_price など、意図が見える。 |
