C言語のきほん|ローカル・グローバル・静的変数の整理

変数の役割を整理すると、C言語のコードはもっと読みやすく、もっと壊れにくくなる

C言語を学んでいると、変数についていろいろな呼び方が出てきます。
ローカル変数、グローバル変数、自動変数、静的変数、外部変数など、似ているようで少しずつ意味が違う言葉が登場するので、最初は混乱しやすいところです。

でも、ここは一度きちんと整理しておくと、とても見通しがよくなります。
なぜなら、変数はただ値を入れる箱ではなく、

  • どこで宣言するか
  • どこまで使えるか
  • いつ作られて、いつ消えるか
  • 前回の値を覚えるかどうか

といった違いによって、ふるまいが大きく変わるからです。

特にC言語では、同じ int 型の変数でも、

  • 関数に入るたびに作り直される変数
  • 関数をまたいでも値を保持する変数
  • どの関数からも見える変数

があり、それぞれ向いている用途が違います。

ここでは、ローカル変数、自動変数、グローバル変数、静的変数を整理しながら、どんな場面でどれを使うのが自然なのかを丁寧に見ていきます。
最後には、実際のサンプルプログラムを通して、動きの違いもはっきり確認できるようにしていきましょう。

まずは用語を整理しよう

変数の呼び方は、何に注目するかで少し変わります。
同じ変数でも、「スコープの観点」と「記憶クラスの観点」で別の呼び方をすることがあります。

最初に全体像を表で見ておきましょう。

呼び方注目していること代表例
ローカル変数どこで使えるか スコープ関数内やブロック内の変数
グローバル変数関数の外にあることファイル内で広く見える変数
自動変数いつ作られていつ消えるか普通のローカル変数
静的変数値を保持するかどうかstatic 付き変数
外部変数他ファイルとのつながりextern で参照する変数

今回の中心は、次の3つです。

  • ローカル変数 自動変数
  • グローバル変数
  • 静的変数

この3つを整理すると、かなり理解しやすくなります。

ローカル変数 自動変数 とは

ローカル変数は、関数の中や { } で囲まれたブロックの中で宣言される変数です。
そして、普通のローカル変数は多くの場合、自動記憶域期間を持つので、自動変数とも呼ばれます。

特徴

項目内容
宣言場所関数の中、またはブロックの中
スコープ宣言されたブロック内のみ
寿命ブロックに入ってから抜けるまで
初期値初期化しないと不定値
一般的な書き方int a;

使いどころ

ローカル変数 自動変数 は、その場だけで使う一時的な情報に向いています。

  • 合計を計算するための一時変数
  • ループカウンタ
  • 条件分岐で使う補助変数
  • 関数の中だけで意味を持つ値

こうした変数は、外から影響を受けにくく、コードの見通しもよくなるので、とても基本的で大切です。

呼び方の違い

同じ変数でも、何を強調したいかで呼び方が変わります。

  • スコープを強調したいとき
    → ローカル変数
  • 記憶域期間やメモリ管理を強調したいとき
    → 自動変数

グローバル変数 外部変数 とは

グローバル変数は、関数の外側で宣言される変数です。
宣言以降、そのソースファイルの中で広く使えます。

特徴

項目内容
宣言場所関数の外側
スコープ宣言以降、ファイルの終わりまで
寿命プログラム開始から終了まで
初期値初期化しないと数値は 0、ポインタは NULL
用途複数の関数で値を共有したいとき

記述例

int g_total;
static int g_local_only;
extern int g_other_file;

それぞれの意味は次のように整理できます。

書き方意味
int g_total;通常のグローバル変数
static int g_local_only;このファイル内だけで使うグローバル変数
extern int g_other_file;別の場所で定義された変数を参照する宣言

使いどころ

グローバル変数は、複数の関数で共通して使う値に向いています。

  • 共通設定
  • 全体の状態
  • 複数の関数で同期したい情報

ただし、便利な反面、どこで値が変わったのか追いにくくなるので、使いすぎには注意が必要です。

呼び方の違い

  • スコープを強調したいとき
    → グローバル変数
  • extern で外部と結びつくことを強調したいとき
    → 外部変数

静的変数とは

静的変数は、static を付けて宣言する変数です。
この変数は、プログラムの実行中ずっと存在し続け、値を保持します。

ただし、どこに書くかでスコープは変わります。

特徴

項目内容
宣言場所関数の中でも外でも可能
寿命プログラム開始から終了まで
初期値初期化しないと数値は 0、ポインタは NULL
記述方法static int x;

関数内で書いた場合

void func(void)
{
    static int count;
}

この場合、count は func の中だけで使える ローカルな変数ですが、値は関数を抜けても保持されます。

つまり、

  • スコープは狭い
  • 寿命は長い

という少し特徴的な変数になります。

関数外で書いた場合

static int g_value;

この場合は、ファイルスコープを持ちながら、そのファイルの中だけで使える変数になります。
外部ファイルには公開したくないグローバルなデータとして使えます。

使いどころ

静的変数は、前回の値を覚えておきたいときに便利です。

  • 呼び出し回数のカウント
  • 前回の状態の記憶
  • 関数の中だけで持っておきたい履歴情報

3種類をまとめて比較しよう

ここで、ローカル変数、自動変数、グローバル変数、静的変数の違いを整理してみます。

種類宣言場所スコープ寿命初期値
ローカル変数 自動変数関数内、ブロック内そのブロック内ブロックを抜けるまで不定値
グローバル変数関数外宣言以降のファイル内プログラム終了まで0 または NULL
静的ローカル変数関数内、ブロック内そのブロック内プログラム終了まで0 または NULL
static 付きグローバル変数関数外そのファイル内プログラム終了まで0 または NULL

この表を見ると、スコープ寿命 は別の観点だということがよく分かります。

サンプルプログラムで違いを確認しよう

ここでは別のシンプルな例として、呼び出し回数を表示する 3 つの関数を作成します。

ファイル名:13_19_1.c

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

int local_visit(void);
int static_visit(void);
int global_visit(void);

int total_calls;   // グローバル変数

int main(void)
{
    printf("ローカル変数 自動変数\t");
    for (int i = 1; i <= 5; i++) {
        printf("%d ", local_visit());
    }

    printf("\n静的ローカル変数\t");
    for (int i = 1; i <= 5; i++) {
        printf("%d ", static_visit());
    }

    printf("\nグローバル変数\t");
    for (int i = 1; i <= 5; i++) {
        printf("%d ", global_visit());
    }

    printf("\n");

    return 0;
}

// 自動変数で呼び出し回数を数えようとする関数
int local_visit(void)
{
    int count = 0;

    count++;

    return count;
}

// 静的ローカル変数で呼び出し回数を数える関数
int static_visit(void)
{
    static int count;

    count++;

    return count;
}

// グローバル変数で呼び出し回数を数える関数
int global_visit(void)
{
    total_calls++;

    return total_calls;
}

実行結果例

ローカル変数 自動変数    1 1 1 1 1
静的ローカル変数         1 2 3 4 5
グローバル変数           1 2 3 4 5

この結果は何を意味しているのか

この実行結果は、3種類の変数の違いをとてもよく表しています。

local_visit の count

int count = 0;

これは自動変数です。
関数が呼ばれるたびに新しく作られ、毎回 0 から始まります。

そのため、

  • count は 0 で初期化される
  • count++ で 1 になる
  • 関数が終わると消える

という流れを毎回くり返します。

だから結果はずっと

1 1 1 1 1

になります。

static_visit の count

static int count;

これは静的ローカル変数です。
スコープは関数内だけですが、寿命はプログラム終了までです。

そのため、

  • 最初は 0 で初期化される
  • 1 回目で 1
  • 2 回目で 2
  • 3 回目で 3

というように、前回の値を覚えたまま増えていきます。

global_visit の total_calls

int total_calls;

これはグローバル変数です。
プログラム開始時に 0 で初期化され、どの関数からでも使えます。

global_visit ではこの変数を直接増やしているので、static 変数と同じように通算カウントができます。

同じ名前があっても衝突しない理由

この種の例では、変数名が同じでも問題ないことがあります。
たとえば元のサンプルでは、グローバル変数 count と、関数内の count がありました。

これは、ローカル変数がグローバル変数より優先されるからです。

たとえば関数の中で

int count = 0;

と書いた場合、その関数の中で count と書けば、グローバル変数ではなくローカル変数を指します。

ただし、これは文法上可能というだけで、実際には混乱を招きやすいです。
記事や教材では仕組みを理解するために役立ちますが、実務的には名前を重複させないほうが読みやすくなります。

図で整理すると理解しやすい

文章だけでは少し抽象的なので、図にするとかなり分かりやすくなります。

この図では、local_visit の count は関数のたびに新しく作られるので、毎回 1 になることが分かります。
一方、static_visit の count は関数をまたいで値を保持するので、呼び出すたびに増えていきます。
また、global_visit は関数の外にある total_calls を使っているため、同じように通算で増えていきます。

どの変数を選ぶのが自然なのか

では、実際にどう使い分けるのがよいのでしょうか。
ここがとても大事です。

一時的な計算ならローカル変数 自動変数

その場だけで使う値なら、普通のローカル変数が基本です。

  • 一時的な計算結果
  • ループ用の変数
  • 関数の中だけで完結する情報

こうした用途では、ローカル変数がいちばん自然です。

前回の値を関数の中で覚えたいなら静的ローカル変数

関数の中だけで使いたいけれど、前回の値を保持したいなら static が向いています。

  • 呼び出し回数の記録
  • 前回の状態の保持
  • 関数内部だけで完結させたい履歴

今回の「通算カウント」の例では、これがいちばん自然です。

多くの関数で共有したいならグローバル変数

複数の関数で同じ値を共有する必要があるなら、グローバル変数も選択肢になります。
ただし、使いすぎると見通しが悪くなるので、必要最小限にしたいところです。

実践問題

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

関数宣言

int find_min(int n);

機能
呼び出されるたびに引数 n を比較し、それまでに渡された値の最小値を記録する。
ただし、引数 n は 0以上の整数値 とする。

返却値
呼び出された時点での最小値

実行結果例

整数値を入力してください(終了条件:負数) > 34
整数値を入力してください(終了条件:負数) > 22
整数値を入力してください(終了条件:負数) > 65
整数値を入力してください(終了条件:負数) > -1

最小値は22です。

解答例

ファイル名:13_19_2.c

// これまでに入力された最小値を記録するプログラム
#include <stdio.h>

int find_min(int n);

int main(void)
{
    int n;
    int result = 0;

    while (1) {
        printf("整数値を入力してください(終了条件:負数) > ");
        scanf("%d", &n);

        if (n < 0) {
            break;
        }

        result = find_min(n);
    }

    printf("\n最小値は%dです。\n", result);

    return 0;
}

// これまでの最小値を保持する関数
int find_min(int n)
{
    static int min = -1;

    if (min == -1 || n < min) {
        min = n;
    }

    return min;
}

解説

この問題では、前回までの最小値を保持する必要があります。
そのため、関数内で普通のローカル変数を使うと、毎回リセットされてしまいます。

そこで、

static int min = -1;

という静的ローカル変数を使っています。
これにより、関数の中だけで使いながら、前回の値を保持できます。

実践問題

グローバル変数を使って、ポイントを管理するプログラムを作成してください。

仕様

  • グローバル変数 point の初期値は 5 とする
  • 5回の試行を行う
  • 各試行で 1〜3 のランダムな値だけ増加または減少する
  • 増減は 50% の確率で決める
  • 上限は 15、下限は 0 とする

関数宣言

void add_point(int value);
void sub_point(int value);

解答例

ファイル名:13_19_3.c

// グローバル変数でポイントを管理するプログラム
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void add_point(int value);
void sub_point(int value);

int point = 5;

int main(void)
{
    srand((unsigned int)time(NULL));

    printf("ポイントの初期値は %d です。\n\n", point);

    for (int i = 1; i <= 5; i++) {
        int value = rand() % 3 + 1;

        if (rand() % 2 == 0) {
            add_point(value);
        }
        else {
            sub_point(value);
        }

        printf("現在 %d です。\n\n", point);
    }

    printf("最終的にポイントは %d です。\n", point);

    return 0;
}

void add_point(int value)
{
    printf("ポイントは %d 増加しました。\n", value);

    point += value;

    if (point > 15) {
        printf("上限値15を超えるので補正します。\n");
        point = 15;
    }
}

void sub_point(int value)
{
    printf("ポイントは %d 減少しました。\n", value);

    point -= value;

    if (point < 0) {
        printf("下限値0を下回るので補正します。\n");
        point = 0;
    }
}

解説

この問題では、どの関数からも同じ point を操作したいので、グローバル変数が使われています。
add_point と sub_point が同じ point を共有しているため、関数呼び出しのたびに状態が引き継がれます。

実践問題

静的変数を使って、関数が呼ばれるたびに次の番号を返すプログラムを作成してください。

関数宣言

int next_number(void);

機能
最初は 1 を返し、その後は呼び出されるたびに 1 ずつ大きい値を返す。

返却値
連番

実行結果例

個数を入力してください > 5

連番:
1 2 3 4 5

解答例

ファイル名:13_19_4.c

// 静的変数で連番を返すプログラム
#include <stdio.h>

int next_number(void);

int main(void)
{
    int n;

    printf("個数を入力してください > ");
    scanf("%d", &n);

    printf("\n連番:\n");

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

    printf("\n");

    return 0;
}

int next_number(void)
{
    static int number = 1;

    int current = number;
    number++;

    return current;
}

解説

この問題では、前回の number を覚えておく必要があるため、static を使っています。
普通のローカル変数だと毎回 1 に戻ってしまうので、連番にはなりません。
静的ローカル変数は、「関数内だけで使いたいけれど、値は保持したい」という場面にとても向いています。

実践問題

以下の仕様を満たして、配列を利用したスタックをグローバル変数で実装してください。

仕様

  • 最大要素数 5 のスタック配列を用意する
  • top というグローバル変数で現在の要素数を管理する
  • 次の関数を作成する

関数宣言

void push(int x);
int pop(void);
int is_empty(void);
int is_full(void);
void print_stack(void);

機能

  • push
    要素を追加する。満杯ならエラー表示
  • pop
    要素を取り出して返す。空なら -1 を返す
  • is_empty
    空なら 1、そうでなければ 0
  • is_full
    満杯なら 1、そうでなければ 0
  • print_stack
    スタックの中身を表示する

解答例

ファイル名:13_19_5.c

// グローバル変数で固定長スタックを実装するプログラム
#include <stdio.h>

#define SIZE 5

void push(int x);
int pop(void);
int is_empty(void);
int is_full(void);
void print_stack(void);

int stack[SIZE];
int top = 0;

int main(void)
{
    print_stack();

    push(10);
    push(20);
    push(30);
    push(40);
    push(50);
    push(60);

    print_stack();

    printf("取り出した値: %d\n", pop());
    printf("取り出した値: %d\n", pop());

    print_stack();

    push(99);
    print_stack();

    printf("取り出した値: %d\n", pop());
    printf("取り出した値: %d\n", pop());
    printf("取り出した値: %d\n", pop());
    printf("取り出した値: %d\n", pop());
    printf("取り出した値: %d\n", pop());

    return 0;
}

void push(int x)
{
    printf("%d を追加します。\n", x);

    if (is_full()) {
        printf("スタックが満杯です。追加できません。\n");
        return;
    }

    stack[top] = x;
    top++;
}

int pop(void)
{
    if (is_empty()) {
        printf("スタックが空です。取り出せません。\n");
        return -1;
    }

    top--;
    return stack[top];
}

int is_empty(void)
{
    return top == 0;
}

int is_full(void)
{
    return top == SIZE;
}

void print_stack(void)
{
    printf("スタックの状態:要素数 = %d\n", top);

    if (is_empty()) {
        printf("要素 = なし\n\n");
        return;
    }

    printf("要素 = ");
    for (int i = 0; i < top; i++) {
        printf("%d ", stack[i]);
    }
    printf("\n\n");
}

解説

この問題では、stack 配列と top をグローバル変数にしています。
そのため、push、pop、print_stack など、どの関数からも同じデータを共有できます。

こうしたデータ構造の実装では、複数の関数が同じ状態を扱う必要があるため、グローバル変数を使う設計が比較的理解しやすいです。
ただし、実際には外から自由に書き換えられる危険もあるため、規模が大きくなると設計には注意が必要です。

最後に押さえておきたい整理

ここまでの内容を短く整理すると、次のようになります。

変数の種類こんなときに向いている
ローカル変数 自動変数その場だけで使う一時的な情報
静的ローカル変数関数内だけで使いたいが、前回の値は保持したい
グローバル変数複数の関数で同じ値を共有したい

特に初心者のうちは、

  • 基本はローカル変数
  • 値を保持したいときだけ static
  • 本当に共有が必要なときだけグローバル変数

という順番で考えると、コードが整理しやすくなります。

変数はただ宣言するだけではなく、どこに置くかで意味が変わる という点を意識すると、C言語の理解がぐっと深まります。