C言語基礎|自動記憶域期間と静的記憶域期間

C言語の変数って、「宣言したらずっと生きてる」わけじゃないんです。
ある変数は関数を抜けた瞬間に消えるし、ある変数はプログラム終了までずっと残り続けます。

この“変数の寿命”を表す考え方が 記憶域期間(storage duration)
ここを理解すると、次のようなモヤモヤが一気に晴れます。

  • 関数を呼び出すたびに、変数が毎回0に戻るのはなぜ?
  • static を付けると、なぜ前回の値を覚えているの?
  • グローバル変数がずっと生きてるのはなぜ?

記憶域期間とは?

記憶域期間=オブジェクト(変数)がいつ作られて、いつ破棄されるか です。
有効範囲(scope)が「名前が通用する範囲」だったのに対して、記憶域期間は「実体が生きてる期間」です。

有効範囲と記憶域期間の違い

観点何を決める?
有効範囲(scope)その名前が使える範囲ブロックの中だけ見える変数
記憶域期間(storage duration)その変数が存在する寿命関数を抜けたら消える変数

自動記憶域期間(automatic storage duration)

関数の中で、普通に宣言した変数はだいたいこれです。

自動記憶域期間の特徴

  • 宣言位置に到達したときに生成
  • ブロックを抜けると破棄
  • 初期化しなければ 不定値(ゴミ値の可能性)

例(イメージ)

void f(void)
{
    int a = 0;  // ここに来たら生成されて0で初期化
}              // ここでaは破棄

静的記憶域期間(static storage duration)

次の2つは静的記憶域期間になります。

  • 関数の外で宣言された変数(いわゆるグローバル変数)
  • 関数の中でも static を付けて宣言した変数

静的記憶域期間の特徴

  • main実行前の準備段階で生成
  • プログラム終了まで存在
  • 初期化しなければ 自動的に0で初期化(配列なら全要素0)

サンプルプログラム

元の ax / sx / fx ではなく、呼び出し回数カウンタの例に変えます。
「毎回リセットされるカウンタ」と「覚えているカウンタ」を並べて比較します。

自動と静的の違いを確認する

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

// 自動記憶域期間と静的記憶域期間(呼び出し回数の比較)

#include <stdio.h>

int total_calls = 0;   // 静的記憶域期間(ファイル有効範囲)

void report(void)
{
    static int saved = 0;  // 静的記憶域期間(ブロック有効範囲)
    int reset = 0;         // 自動記憶域期間(ブロック有効範囲)

    printf("reset=%d  saved=%d  total_calls=%d\n", reset++, saved++, total_calls++);
}

int main(void)
{
    puts("reset saved total_calls");
    puts("----------------------");

    for (int i = 0; i < 5; i++)
        report();

    puts("----------------------");
    return 0;
}

実行イメージ

reset saved total_calls
----------------------
reset=0  saved=0  total_calls=0
reset=0  saved=1  total_calls=1
reset=0  saved=2  total_calls=2
reset=0  saved=3  total_calls=3
reset=0  saved=4  total_calls=4
----------------------

なぜこうなる?をイメージで理解しよう

変数が「いつ作られ」「いつ消える」か

プログラム開始前
  total_calls と saved が生成される(0で初期化)

main開始
  i が生成される(ループ用)

report呼び出し(1回目)
  reset が生成される(0で初期化)
  表示 → resetは1になる
report終了
  reset は破棄される

report呼び出し(2回目)
  reset がまた生成される(0で初期化に戻る)
  saved と total_calls は前回の続き

補足(図のポイント)

  • reset は report の中だけに存在する “はかない命”
    → 呼び出すたびに作り直されるので、毎回0から
  • saved は static 付きなので “ずっと生きる”
    → 1回目は0、2回目は1…と増え続ける
  • total_calls は関数の外なので “ずっと生きる”
    → saved と同じく増え続ける

自動と静的のまとめ

オブジェクトの記憶域期間

項目自動記憶域期間静的記憶域期間
生成宣言に到達したときmain実行前の準備段階
破棄ブロックを抜けたときプログラム終了時
初期化なし不定値になり得る0(数値)、0.0(実数)、配列は全要素0

登場する命令の書式と役割

今回出てきた要素まとめ

命令・要素書式何をする?
staticstatic 型 変数名;変数に静的記憶域期間を与える(値を保持し続ける)
printfprintf(書式, …);値を表示する
putsputs(文字列);文字列を表示して改行
forfor (初期化; 条件; 更新) 文繰り返し
++変数++変数を1増やす(式の値は増やす前の値)

auto と register について(今どきの感覚で)

  • auto は書いても書かなくても同じ(昔の名残)
  • register は「レジスタに置いてね」というお願い
    ただし今はコンパイラ最適化が賢いので、実務ではほぼ使いません

例(意味はあるが、今は出番少なめ)

register int k = 0;

静的記憶域期間は「勝手に0初期化」される

静的記憶域期間の変数は、初期化を書かなくても 0 に初期化されます。
(数値型は0、doubleは0.0、配列は全要素0)

演習問題

演習6-14:配列の0初期化確認

静的記憶域期間をもつ int 配列 data の全要素が 0 で初期化されることを確認するプログラムを作成せよ。
要素数は 4 とする。

解答例

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

#include <stdio.h>

static int data[4];   // 全要素が0で初期化される

int main(void)
{
    for (int i = 0; i < 4; i++)
        printf("data[%d] = %d\n", i, data[i]);

    return 0;
}

解説
data は static 付きなので静的記憶域期間です。初期化子を書かなくても、main開始前に全要素が0になります。
もし同じことを関数内の普通の配列(自動記憶域期間)でやると、初期化しない限り不定値になり得ます。

演習6-15:呼び出し回数の表示

関数 show_count を作成せよ。呼び出されるたびに、1回目、2回目、3回目…と表示する。
関数の形は次のとおり。

void show_count(void);

解答例

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

#include <stdio.h>

void show_count(void)
{
    static int count = 0;
    count++;
    printf("show_count : %d回目\n", count);
}

int main(void)
{
    show_count();
    show_count();
    show_count();
    return 0;
}

解説
count を static にすることで、show_count を抜けても count の値が保持されます。
もし static を付けないと、毎回countが作り直されて同じ表示になってしまいます。