C言語のきほん|ローカル変数とグローバル変数

変数の見える範囲がわかると、C言語のコードはもっと整理しやすくなる

C言語でプログラムを書いていると、同じ変数名が別の場所にも出てきたり、ある関数では使えるのに別の場所では使えなかったりすることがあります。
この違いをきちんと理解するために大切なのが、スコープ 有効範囲記憶クラス の考え方です。

特に最初にしっかり押さえておきたいのが、ローカル変数グローバル変数 の違いです。

  • 関数の中だけで使える変数
  • ソースファイル全体で使える変数

この違いが分かると、
「なぜこの変数はここで使えないのか」
「同じ名前なのに別の値を持っているのはなぜか」
「なぜ関数をまたいで値が残るように見えるのか」
といった疑問がすっきり整理できます。

変数はただ宣言すればよいというものではなく、どこで宣言するか によって使える範囲や振る舞いが大きく変わります。
ここでは、ローカル変数とグローバル変数の違いを中心に、スコープの基本をやさしく丁寧に見ていきましょう。

スコープ 有効範囲 とは

スコープとは、変数や関数を使える範囲 のことです。
簡単にいうと、「その名前がどこまで見えるか」というルールです。

C言語では、変数のスコープは宣言した場所によって変わります。
代表的なものは次の2つです。

種類意味
ブロックスコープ{ } で囲まれたブロックの中で有効
ファイルスコープソースファイル全体で有効

この2つを理解すると、ローカル変数とグローバル変数の違いが見えてきます。

ローカル変数とは

関数の中や、if 文、for 文などの { } で囲まれたブロックの中で宣言された変数 は、そのブロックの中でだけ有効です。
こうした変数を一般に ローカル変数 と呼びます。

たとえば、次のような変数です。

int main(void)
{
    int score = 80;
    return 0;
}

この score は main 関数の中で宣言されているので、main 関数の中でだけ使えます。
func 関数など別の関数からは使えません。

ローカル変数の基本を別のシンプルな例で見てみよう

元の例では main 関数と func 関数の両方に a がありました。
ここでは別の分かりやすい例として、main 関数と show_level 関数に同じ名前の level 変数がある例に置き換えてみます。

ファイル名:13_16_1.c

// ローカル変数の違いを確認するプログラム
#include <stdio.h>

void show_level(void);

int main(void)
{
    int level = 20;   // main関数内だけで有効なローカル変数

    show_level();

    printf("[main level] アドレス: %p\n値: %d\n", (void *)&level, level);

    return 0;
}

void show_level(void)
{
    int level = 5;    // show_level関数内だけで有効なローカル変数

    printf("[show_level level] アドレス: %p\n値: %d\n", (void *)&level, level);
}

実行結果例
アドレスは環境によって異なります。

[show_level level] アドレス: 000000000061FE14
値: 5
[main level] アドレス: 000000000061FE4C
値: 20

この例から分かること

このプログラムでは、main 関数にも show_level 関数にも、どちらも level という名前の変数があります。
でも、この2つは同じ変数ではありません。

理由は、宣言された場所が違うから です。

変数名宣言場所有効範囲
main の levelmain 関数の中main 関数の中だけ
show_level の levelshow_level 関数の中show_level 関数の中だけ

実行結果を見るとアドレスも違っています。
つまり、名前が同じでも、メモリ上では別の変数です。

ここがローカル変数を理解する大事なポイントです。
同じ名前でも、スコープが違えば別物 です。

ローカル変数はブロックを抜けると使えない

ローカル変数は、その変数が宣言されたブロックを抜けると使えなくなります。

たとえば、for 文の中で宣言した変数は、その for 文の外では使えません。

for (int i = 0; i < 3; i++) {
    printf("%d\n", i);
}

/* ここで i は使えない */

このように、ローカル変数の寿命ではなく、まずは見える範囲としてのスコープが制限されているわけです。

ブロックの中と外で同じ名前を使える

次に、関数の中のさらに小さなブロックを見てみましょう。
元の例では for 文の中と外に n がありました。
ここでは、外側の score と、ループ内の score の例に置き換えます。

ファイル名:13_16_2.c

// ブロックごとのローカル変数を確認するプログラム
#include <stdio.h>

int main(void)
{
    int score = 50;   // 外側のローカル変数

    printf("[ループ前のscore] アドレス: %p\n値: %d\n\n", (void *)&score, score);

    for (int i = 1; i <= 3; i++) {
        int score = 100;   // ループ内のローカル変数

        printf("[ループ中のscore] アドレス: %p\n値: %d\n", (void *)&score, score);
        printf("[ループ中のi] アドレス: %p\n値: %d\n", (void *)&i, i);
    }

    printf("\n[ループ後のscore] アドレス: %p\n値: %d\n", (void *)&score, score);

    return 0;
}

実行結果例
アドレスは環境によって異なります。

[ループ前のscore] アドレス: 000000000061FE4C
値: 50

[ループ中のscore] アドレス: 000000000061FE44
値: 100
[ループ中のi] アドレス: 000000000061FE48
値: 1
[ループ中のscore] アドレス: 000000000061FE44
値: 100
[ループ中のi] アドレス: 000000000061FE48
値: 2
[ループ中のscore] アドレス: 000000000061FE44
値: 100
[ループ中のi] アドレス: 000000000061FE48
値: 3

[ループ後のscore] アドレス: 000000000061FE4C
値: 50

内側のスコープが優先される

この例では、外側にも score があり、ループ内にも score があります。
ループの中で score と書いたときに使われるのは、より内側で宣言された score です。

これを整理すると、次のようになります。

場所score が指すもの
ループの外外側の score 50
ループの中内側の score 100

このように、同じ名前が重なった場合は、内側のスコープの変数が優先されます
これをシャドーイングのように説明することもありますが、まずは「内側が優先」と覚えると分かりやすいです。

ただし、同じスコープの中で同じ名前を重ねて宣言することはできません

C99 以降では途中で宣言できる

ローカル変数について、学習中によく知っておきたいのが宣言位置のルールです。

昔のC言語では、ブロックの先頭でしか変数を宣言できない時代がありました。
でも C99 以降では、必要な場所で変数を宣言できる ようになりました。

たとえば、for 文の中で

for (int i = 0; i < 3; i++)

のように書けるのもそのためです。

今の学習では、この書き方で進めて問題ありません。

仮引数もローカル変数と考えてよい

ここでは主に int a; のような普通の変数を見てきましたが、関数の仮引数もローカル変数として考えることができます。

たとえば、

int add(int a, int b)

の a と b は、その関数の中でだけ使える値です。
main 関数の外から直接 a や b を使うことはできません。

つまり仮引数も、関数のローカルな領域で使われる変数 です。

グローバル変数とは

次にグローバル変数です。

関数の外側、ファイルの先頭などで宣言された変数 は、ファイルスコープを持ちます。
このような変数を一般に グローバル変数 と呼びます。

たとえば、次のような形です。

int total_count = 0;

これが関数の外に書かれていれば、その変数はそのソースファイルの中から広く使えます。

グローバル変数の例を別の内容で見てみよう

元の例では g_val1 と g_val2 が使われていました。
ここでは別のシンプルな例として、game_score と bonus_point という名前で置き換えてみます。

ファイル名:13_16_3.c

// グローバル変数を確認するプログラム
#include <stdio.h>

void update_score(void);

int game_score = 100;   // グローバル変数

int main(void)
{
    printf("[main game_score] アドレス: %p\n値: %d\n\n", (void *)&game_score, game_score);

    update_score();

    printf("[main game_score] アドレス: %p\n値: %d\n", (void *)&game_score, game_score);

    return 0;
}

int bonus_point = 20;   // グローバル変数

void update_score(void)
{
    printf("[update_score game_score] アドレス: %p\n値: %d\n", (void *)&game_score, game_score);
    printf("[update_score bonus_point] アドレス: %p\n値: %d\n\n", (void *)&bonus_point, bonus_point);

    game_score += bonus_point;
}

実行結果例
アドレスは環境によって異なります。

[main game_score] アドレス: 00007FF700003000
値: 100

[update_score game_score] アドレス: 00007FF700003000
値: 100
[update_score bonus_point] アドレス: 00007FF700003004
値: 20

[main game_score] アドレス: 00007FF700003000
値: 120

この例から分かること

このプログラムでは、game_score は main 関数でも update_score 関数でも使えています。
しかも、表示されたアドレスが同じです。

これはつまり、どちらから見ても同じ変数を使っている ということです。

ローカル変数では関数ごとに別物でしたが、グローバル変数では関数をまたいで同じ変数を共有できます。

また、update_score 関数で

game_score += bonus_point;

としているので、関数呼び出し後に main 関数で見ると、game_score の値が 120 に変わっています。

ここがグローバル変数の大きな特徴です。
どこかで変更すると、同じ変数を見ている他の場所にも影響が出る のです。

グローバル変数の有効範囲は宣言以降

ここで注意したいのが、グローバル変数は「ファイル全体で使える」といっても、宣言より前では使えない という点です。

たとえば、次のような順番なら、

int g1 = 10;

int main(void)
{
    printf("%d\n", g1);
}

main から g1 を使えます。

でも、まだ宣言されていない変数を、その前の行で使うことはできません。
つまり、グローバル変数も 宣言された位置以降 で見えるようになると考えると分かりやすいです。

ローカル変数とグローバル変数の違いを表で整理しよう

ここまでの内容を、表でまとめると次のようになります。

項目ローカル変数グローバル変数
宣言場所関数内、ブロック内関数の外
スコープ宣言されたブロック内宣言以降のファイル全体
他の関数から見えるか見えない見える
同名の変数との関係別スコープなら別物共有される
影響範囲比較的狭い比較的広い

この表を見ると、ローカル変数は狭い範囲だけで使うのに向いていて、グローバル変数は広い範囲で共有したいときに使えることが分かります。

ローカル変数とグローバル変数の違いは、図で見るとさらに分かりやすいです。

この図では、グローバル変数が関数の外に置かれ、複数の関数から同じ変数を共有している様子を表しています。
一方、各関数の中にあるローカル変数は、その関数の中だけに閉じた別々の変数として描かれています。

グローバル変数は便利だが注意も必要

グローバル変数は、どこからでも使えるので便利です。
でも、そのぶん注意も必要です。

便利な点注意点
複数の関数で共通の値を使えるどこで値が変わったか追いにくい
引数で毎回渡さなくてよい影響範囲が広くなる
状態を共有しやすいバグの原因になりやすいことがある

特に学習の初期段階では、グローバル変数を使いすぎると、どこで値が変化したのか分かりにくくなることがあります。
そのため、まずは 必要な値は引数で渡し、必要な結果は return やポインタで返す という基本を大事にしつつ、グローバル変数は本当に共有したい値に限って使う、という考え方が大切です。

ローカル変数のほうが整理しやすい場面

ローカル変数は有効範囲が狭いので、コードを読みやすくしやすいです。
なぜなら、その変数がどこで使われ、どこまで有効かが分かりやすいからです。

たとえば、

  • その関数の中だけで使う計算用の変数
  • ループの回数を数える変数
  • 一時的な作業用の変数

などは、ローカル変数にするのが自然です。

グローバル変数のほうが向いている場面

一方で、複数の関数で共通して使う値なら、グローバル変数が役立つこともあります。

たとえば、

  • ゲーム全体のスコア
  • 共通設定のフラグ
  • 何度も参照する状態情報

などです。

ただし、グローバル変数は便利なぶん、設計をよく考えて使うことが大切です。

実践問題

次の仕様に従ってプログラムを作成し、main 関数から関数を呼び出して結果を確認してください。

仕様

  • グローバル変数 total_score を宣言する
  • main 関数で total_score の値を表示する
  • add_score 関数で total_score に 15 を加算する
  • add_score 関数の中で、同名ではないローカル変数 bonus を使う
  • 関数呼び出し前後で total_score の変化を確認する

実行結果例

呼び出し前の total_score: 50
関数内の bonus: 15
呼び出し後の total_score: 65

解答例

ファイル名:13_16_4.c

// ローカル変数とグローバル変数の違いを確認するプログラム
#include <stdio.h>

void add_score(void);

int total_score = 50;   // グローバル変数

int main(void)
{
    printf("呼び出し前の total_score: %d\n", total_score);

    add_score();

    printf("呼び出し後の total_score: %d\n", total_score);

    return 0;
}

void add_score(void)
{
    int bonus = 15;   // ローカル変数

    printf("関数内の bonus: %d\n", bonus);

    total_score += bonus;
}

解答例の解説

この問題では、total_score は関数の外で宣言されているのでグローバル変数です。
そのため、main 関数でも add_score 関数でも使えます。

一方、bonus は add_score 関数の中で宣言されているのでローカル変数です。
この bonus は add_score 関数の中でしか使えません。

このプログラムで特に大事なのは、

total_score += bonus;

によって、関数の中でグローバル変数を書き換えていることです。
だから、main 関数に戻ったあとでも total_score の値が更新されています。

学習のコツ

ローカル変数とグローバル変数を理解するときは、まず その変数がどこで宣言されているか に注目するのがいちばん大切です。

見るポイント確認すること
関数の中で宣言されているかローカル変数
関数の外で宣言されているかグローバル変数
どこまで見えるかスコープ
どこで変更されるか影響範囲

特に、同じ名前が出てきたときは
同じ変数なのか、別のスコープの別変数なのか
を意識すると、かなり整理しやすくなります。

また、日ごろコードを読むときにも、変数を見つけたら
「これはこの関数だけのものかな、それとも全体で共有しているものかな」
と考える習慣をつけると、スコープの理解がぐっと深まります。