6日でできる 新C言語入門|C言語の自動変数・外部変数・静的変数

変数は、どこで生まれ、どこから見えて、いつまで残るのか。
自動変数・外部変数・静的変数を理解して、C言語のメモリ管理を一歩深く学ぼう。

C言語で変数を使うとき、変数には単に値を入れるだけでなく、どこに保存されるのか、どこから参照できるのか、いつまで値が残るのかという性質があります。

このような変数の性質を決める考え方が、記憶クラスです。記憶クラスは、ストレージクラスとも呼ばれます。

普段よく使うローカル変数は、関数の中で一時的に作られ、関数が終わると消える自動変数です。関数の外側で宣言するグローバル変数は、プログラムの実行中ずっと存在する外部変数です。また、staticを付けた静的変数は、見える範囲を限定しながら値を保持できる特別な性質を持ちます。

この違いを理解しておくと、変数をどこで宣言すればよいか、関数をまたいで値を共有してよいか、値を保持したい場合にどう書けばよいかが判断しやすくなります。

この記事では、代表的な記憶クラスである自動変数、外部変数、静的変数を中心に、メモリ領域、有効範囲、生存期間、初期値の違いを整理しながら、サンプルプログラムで確認していきます。

記憶クラスとは何か

記憶クラスとは、変数がどのような性質を持つかを決める分類です。

特に大切なのは、次の3つです。

観点意味
記憶領域変数がメモリ上のどこに置かれるか
有効範囲その変数をどこから参照できるか
生存期間その変数がいつ作られ、いつ消えるか
初期値初期化しなかった場合にどのような値になるか

C言語では、同じint型の変数でも、宣言する場所やstaticなどの指定子によって性質が変わります。

たとえば、関数の中で宣言したint型変数と、関数の外で宣言したint型変数では、使える範囲も値が残る期間も異なります。

代表的な3つの記憶クラス

この記事で中心に扱う記憶クラスは、自動変数、外部変数、静的変数です。

記憶クラス主な指定子主な記憶領域有効範囲初期化しない場合
自動変数auto、省略可スタック領域宣言したブロック内不定値
外部変数なし静的領域複数の関数から参照可能0
静的変数static静的領域宣言場所によって異なる0

自動変数は、関数の中でよく使う普通のローカル変数です。
外部変数は、関数の外側で宣言して、複数の関数から使える変数です。
静的変数は、staticを付けることで、値の保持や参照範囲の制限ができる変数です。

図:記憶クラスとメモリ領域の関係

この図から分かること

この図から分かるのは、変数の種類によって置かれるメモリ領域が異なるということです。

自動変数は主にスタック領域に置かれ、関数の呼び出し中だけ存在します。外部変数や静的変数は静的領域に置かれ、プログラムの実行中ずっと存在します。この違いが、値が消えるか残るか、有効範囲が広いか狭いかに関係します。

自動変数は関数内で一時的に使う変数

自動変数は、関数の中やブロックの中で宣言される変数です。

普段何気なく書いているローカル変数の多くは、自動変数です。

int score = 80;

auto指定子を付けることもできますが、通常は省略します。

auto int score = 80;

どちらも、自動変数として扱われます。

自動変数の特徴は、関数が呼び出されたときに作られ、関数の処理が終わると消えることです。

項目自動変数の特徴
宣言場所関数内、ブロック内
主な記憶領域スタック領域
生存期間宣言されたブロックの実行中
有効範囲宣言されたブロック内
初期化しない場合不定値

初期化しない自動変数には、どのような値が入っているか分かりません。そのため、自動変数は使う前に初期化する習慣をつけると安全です。

自動変数を確認するプログラム

次のプログラムでは、main関数の中で自動変数を宣言し、値を表示します。

プロジェクト/ファイル名: Lesson63_1/main.c

#include <stdio.h>

int main(void) {
    auto int score = 85; // autoは通常省略される
    int bonus = 15;      // この変数も自動変数

    printf("自動変数scoreの値は%dです。\n", score);
    printf("自動変数bonusの値は%dです。\n", bonus);
    printf("合計点は%dです。\n", score + bonus);

    return 0;
}

実行結果

自動変数scoreの値は85です。
自動変数bonusの値は15です。
合計点は100です。

このプログラムでは、scoreとbonusがmain関数の中で宣言されています。

scoreにはautoを付けていますが、C言語では関数内のローカル変数は通常、自動変数として扱われます。そのため、bonusも自動変数です。

autoは現在のC言語学習では、意味を確認するために見ることはありますが、実際のコードでは省略されることがほとんどです。

自動変数は関数を抜けると消える

自動変数は、宣言された関数やブロックの中だけで使えます。

たとえば、ある関数の中で宣言した変数は、その関数の外から直接使えません。

void sample(void) {
    int count = 10;
}

このcountはsample関数の中だけで使える変数です。sample関数の外からcountを直接参照することはできません。

また、関数が呼び出されるたびに新しく作られ、関数が終わると消えます。前回の値を自動的に保持するわけではありません。

この性質は、計算途中の値や一時的な処理結果を保存するのに向いています。

外部変数は複数の関数から参照できる変数

外部変数は、関数の外側で宣言する変数です。グローバル変数と呼ばれることもあります。

関数の外側で宣言されているため、同じファイル内の複数の関数から参照できます。

int totalCount = 0;

このような変数は、プログラムの実行中ずっと存在します。初期化しない場合は、自動的に0で初期化されます。

項目外部変数の特徴
宣言場所関数の外側
主な記憶領域静的領域
生存期間プログラムの実行中ずっと
有効範囲宣言位置以降の複数関数から参照可能
初期化しない場合0

外部変数は、複数の関数で同じ値を共有したいときに使えます。

ただし、どの関数からでも変更できるようにすると、どこで値が変わったのか追いにくくなることがあります。便利ですが、使いすぎには注意が必要です。

外部変数を確認するプログラム

次のプログラムでは、関数の外側で外部変数を宣言し、main関数と別の関数から参照します。

プロジェクト/ファイル名: Lesson63_2/main.c

#include <stdio.h>

int totalStock = 5; // 外部変数

void print_stock(void);
void add_stock(int amount);

int main(void) {
    printf("main関数: totalStock=%d\n", totalStock);

    add_stock(3);
    print_stock();

    return 0;
}

void print_stock(void) {
    printf("現在の在庫数は%dです。\n", totalStock);
}

void add_stock(int amount) {
    totalStock += amount;
    printf("在庫を%d個追加しました。\n", amount);
}

実行結果

main関数: totalStock=5
在庫を3個追加しました。
現在の在庫数は8です。

このプログラムでは、totalStockが関数の外側で宣言されています。

main関数ではtotalStockの値を表示しています。
add_stock関数ではtotalStockの値を変更しています。
print_stock関数ではtotalStockの値を表示しています。

このように、外部変数を使うと、複数の関数で同じ値を共有できます。

図:外部変数は複数の関数から共有できる

この図から分かること

この図から分かるのは、外部変数が複数の関数から参照や変更をされる共有データになるということです。

totalStockはmain関数からも、add_stock関数からも、print_stock関数からも使えます。関数間で状態を共有したいときには便利ですが、変更できる場所が増えるほど、値の変化を追いにくくなります。そのため、外部変数は必要な範囲に絞って使うことが大切です。

外部変数を使うときの注意点

外部変数は、複数の関数で共有できるため便利です。しかし、便利だからといって何でも外部変数にすると、プログラムの見通しが悪くなります。

たとえば、ある変数が多くの関数から変更されると、予想外のタイミングで値が変わることがあります。

外部変数を使うときは、次のような点を意識しましょう。

注意点内容
変更場所を増やしすぎないどこで値が変わったか分かりにくくなる
名前を分かりやすくする共有される値だと分かる名前にする
必要なときだけ使う一時的な値なら自動変数で十分
関数の引数も検討する共有せずに引数で渡せる場合もある

外部変数は、プログラム全体の状態を表す値には向いています。
一方、計算途中だけに使う値や、特定の関数内だけで必要な値は、自動変数にしたほうが安全です。

静的変数は値を保持し続ける変数

静的変数は、staticを付けて宣言する変数です。

関数内でstaticを付けて宣言すると、その関数の中でしか参照できないのに、関数が終わっても値が保持されます。

static int counter = 0;

この変数は、関数を抜けても消えません。次に同じ関数が呼ばれたとき、前回の値が残っています。

項目関数内の静的変数の特徴
宣言場所関数内
主な記憶領域静的領域
生存期間プログラムの実行中ずっと
有効範囲宣言した関数内
初期化しない場合0
初期化のタイミングプログラム開始時に1度だけ

自動変数と違い、静的変数は関数の呼び出しごとに作り直されません。

そのため、呼び出し回数を数えたり、前回の状態を覚えておきたい処理に向いています。

静的変数を確認するプログラム

次のプログラムでは、関数が呼び出された回数を静的変数で数えます。

プロジェクト/ファイル名: Lesson63_3/main.c

#include <stdio.h>

void show_call_count(void);

int main(void) {
    show_call_count();
    show_call_count();
    show_call_count();

    return 0;
}

void show_call_count(void) {
    static int counter = 1; // 静的変数

    printf("show_call_count関数は%d回目の呼び出しです。\n", counter);

    counter++;
}

実行結果

show_call_count関数は1回目の呼び出しです。
show_call_count関数は2回目の呼び出しです。
show_call_count関数は3回目の呼び出しです。

このプログラムでは、show_call_count関数の中にstatic int counterがあります。

通常の自動変数であれば、関数が呼び出されるたびに新しく作られ、関数が終わると消えます。しかし、counterは静的変数なので、関数が終わっても値が保持されます。

そのため、1回目の呼び出しでcounterが1から2へ増え、2回目の呼び出しでは2から始まります。3回目も同じように、前回の続きから値が使われます。

自動変数と静的変数の違い

関数内で宣言する変数でも、通常の自動変数とstatic付きの静的変数では大きな違いがあります。

項目自動変数静的変数
宣言例int count = 1;static int count = 1;
記憶領域スタック領域静的領域
関数終了後消える残る
次回呼び出し時新しく作られる前回の値が残る
初期化しない場合不定値0

自動変数は、一時的な作業用の変数に向いています。
静的変数は、関数の中だけで値を覚えておきたい場合に向いています。

図:自動変数と静的変数の生存期間の違い

この図から分かること

この図から分かるのは、自動変数と静的変数では値が残る期間が違うということです。

自動変数は、関数が呼び出されたときに作られ、関数が終わると消えます。一方、静的変数は関数の中で宣言しても静的領域に置かれるため、関数が終わっても値が残ります。関数の中だけで使いたいけれど、前回の値を覚えておきたい場合にstaticが役立ちます。

staticは外部変数にも使える

staticは、関数内のローカル変数だけでなく、関数の外側で宣言する変数にも使えます。

関数の外側でstaticを付けると、その変数は同じソースファイル内だけで使える外部変数になります。

static int fileCount = 0;

このようにすると、fileCountは静的領域に置かれ、プログラムの実行中ずっと存在します。ただし、他のソースファイルからは参照しにくくなります。

大きなプログラムでは、外部変数をむやみに他のファイルへ公開しないために、ファイル内だけで使う変数にstaticを付けることがあります。

宣言場所staticありの意味
関数内関数内だけで使えるが、値は保持される
関数外同じソースファイル内だけで使える外部変数になる

staticは、値の生存期間だけでなく、見える範囲を制限するためにも使われます。

register指定子について

C言語には、register指定子もあります。

register int r = 10;

registerは、変数をCPUのレジスタに置くことを処理系に提案する指定子です。レジスタはCPU内部にある高速な記憶場所です。

ただし、現代のコンパイラは最適化を自動で行うため、registerを付けたからといって必ず高速になるとは限りません。実際にどのように扱われるかは処理系に依存します。

学習段階では、registerは昔からある記憶クラス指定子の1つとして知っておけば十分です。普段のプログラムでは、自動変数、外部変数、静的変数の使い分けを優先して理解しましょう。

extern指定子について

externは、別の場所で定義されている外部変数を参照するために使います。

extern int totalStock;

これは、totalStockという変数の実体は別の場所にあるが、このファイルでも参照する、という意味になります。

複数ファイルに分割したC言語プログラムでは、あるファイルで外部変数を定義し、別のファイルでextern宣言を使って参照することがあります。

指定子役割
extern別の場所にある外部変数を参照するために使う
static変数の有効範囲をファイル内などに制限するために使える

externは便利ですが、外部変数の共有範囲が広がるほど、値の管理が難しくなることがあります。必要な場合に限定して使うとよいでしょう。

3つの変数をどう使い分けるか

自動変数、外部変数、静的変数は、それぞれ向いている場面が違います。

使いたい場面向いている変数
関数内だけで一時的に使いたい自動変数
複数の関数で共有したい外部変数
関数内だけで使い、値を保持したい静的変数
ファイル内だけで共有したい関数外のstatic変数
複数ファイルから参照したい外部変数とextern

基本的には、まず自動変数を使うことを考えると安全です。
関数間で値を共有する必要がある場合は、引数や戻り値で渡せないかを考えます。
それでも共有状態が必要な場合に、外部変数や静的変数を検討します。

変数の有効範囲は、できるだけ狭くするのが読みやすいプログラムを書くための基本です。

初期値の違いに注意する

記憶クラスによって、初期化しなかった場合の値も異なります。

変数の種類初期化しない場合
自動変数不定値
外部変数0
静的変数0

自動変数の不定値は、どのような値になるか分かりません。以前のメモリ内容が残っているように見えることもありますが、それに依存してはいけません。

そのため、自動変数は必ず初期化してから使うようにしましょう。

int count = 0;

外部変数や静的変数は、初期化しない場合は0になります。ただし、読みやすさを考えると、意図が分かるように初期値を書くことも多いです。

記憶クラスを理解すると変数設計が分かりやすくなる

自動変数、外部変数、静的変数は、どれもC言語の変数ですが、性質が大きく異なります。

自動変数は、関数内で一時的に使う変数です。関数の呼び出し中だけ存在し、関数が終わると消えます。通常のローカル変数は、この自動変数として使われます。

外部変数は、関数の外側で宣言する変数です。複数の関数から共有でき、プログラムの実行中ずっと存在します。便利ですが、どこから変更されるか分かりにくくなることがあるため、使う範囲を意識することが大切です。

静的変数は、staticを付けて宣言する変数です。関数内で宣言しても値が保持されるため、呼び出し回数や前回の状態を覚えておきたい処理に向いています。また、関数外でstaticを使うと、同じファイル内に有効範囲を制限できます。

変数を宣言するときは、ただ型だけを見るのではなく、その変数をどこで使いたいのか、いつまで値を残したいのか、どの関数から見えるべきなのかを考えると、より安全で読みやすいC言語プログラムを書けるようになります。