苦しんで覚えるC言語 12.1 関数内で寿命が尽きる変数 P218 – P231

ローカル変数の寿命

これまで変数は値を代入するものとして解説をしてきました。
変数の宣言時には、格納する値の種類に応じて型(intやcharなど)を設定する必要があることも解説済みです。

この変数は関数内、関数外、ファイル内、ファイル外など、有効区間があります。
ここではどこで宣言された変数がどこまで有効範囲を持っているのかを解説したいと思います。
参考書のコードを用いてみましょう。

#include <stdio.h>

int countfunc(void);

int main(void)
{
    countfunc();
    countfunc();
    countfunc();

    return 0;
}

int countfunc(void)
{
    int count = 0;

    count++;
    printf("%d\n", count);

    return count;
}

このプログラムは、main関数から始まりcountfunc()が3回呼ばれて終了になります。
countfunc()関数内は、countという変数がインクリメントされて数値を表示した後、
main関数の呼び出し位置に戻ります。

参考書の通り、実行された結果、count変数は 1 が3回表示されて終了しています。
countfunc()関数内の先頭で、int count = 0; とcountという名前の変数の宣言と同時に、
0を代入して初期化していますので、countfunc()関数が呼ばれる度にcount変数は0が入れられて、count++で 1 になって・・・を関数が呼ばれる度に繰り返していそうです。

では、int count = 0;の初期化をなくしてみると、呼ばれる度に 1 ずつ増加していくのでしょうか?

#include <stdio.h>

int countfunc(void);

int main(void)
{
    countfunc();
    countfunc();
    countfunc();

    return 0;
}

int countfunc(void)
{
    int count;  /* 初期化をなくす */

    count++;
    printf("%d\n", count);

    return count;
}

countfunc()関数内のcount変数の初期化をやめました。
参考書では、5369 という表示結果になっていますので、
5368がcount変数の宣言と同時に格納されて、count++; で 1 が加算されて 5369 になっています。

この説明に補足が必要な点があるのですが、5368がcount変数の宣言と同時に格納されてという点です。
変数は宣言と同時に代入値が無ければ、不定な値が格納されます。
どの様な値が入っているかはわかりません。

参考書にも記載されていますが、3回countfunc()が呼ばれましたが、
3回 5368 が格納されて +1 されるのは偶々です。
5368は毎回違う値だったかもしれません。

ここで知っておいていただきたいのは、
 ・変数は宣言時に初期化しなければ不定値が格納されている
 ・初期化をしない場合も、関数が呼び出された回数だけcount変数の値が増えていかない

何となく最初のcountfunc()で 5368 + 1で 5369 が表示、2回目のcountfunc()で 5369 + 1で 5370 が表示、3回目のcountfunc()で 5370 + 1で 5371 が表示されそうですが、そのようになりません。

これは、変数の生きている範囲が影響します。
関数内で宣言されて作られた変数は、関数の処理が終わると消滅してなくなります。
つまり、関数内だけ有効ということです。

上記のプログラムでは、毎回countfunc()が呼ばれる度に、count変数が生成されて、
末文のreturn count;を処理して、count変数は無くなります。

同一名の変数は別変数となる

関数が複数ある場合、いずれの関数内にも同じ名前の変数を用意するとどうなるでしょうか?
参考書のプログラムに少し追加をして確認してみましょう。

#include <stdio.h>

int countfunc(void);

int main(void)
{
    int count = 0;  /* main関数のcount変数 */

    countfunc();
    countfunc();
    countfunc();

    printf("%d\n", count);

    return 0;
}

int countfunc(void)
{
    int count = 100;

    count++;
    printf("%d\n", count);

    return count;
}

countfunc()関数内のcountは宣言と同時に100を代入しています。
count++で1が加算されて、printf関数では 101 が表示されます。
main関数で3回呼ばれますが、いずれも 101 が表示されます。

main関数にも同じ名前である count 変数を宣言し、同時に0を代入しています。
3回 countfunc()関数が呼ばれた後、printf関数でcount変数の値を表示しています。
ここで表示される値は 0 です。

変数は関数内だけ有効と解説した通り、countfunc()関数内の count 変数と、
main関数内の count 変数は、同じ名前ですが別の変数として扱われます。

基本的に同じ名前の変数は、できるだけ宣言しない様にすべきです。
同名の変数として宣言するのは、for文のカウント変数となる in などが主です。
意味のある変数としては、同じ名前で宣言するのは避ける方が賢明ということになります。

ブロックの範囲が正確な変数の寿命

関数内が変数の寿命と解説していますが、関数は { } の範囲が処理になります。
{ } で囲まれた範囲をブロックと呼ぶのですが、この範囲内が有効範囲となります。

#include <stdio.h>

int main(void)
{
   int value1 = 10;
   int value2 = 20;
   printf("1:value1 %d\n", value1);
   printf("1:value2 %d\n", value2);
   {
       int value1;
       value1 = 30;
       value2 = 40;
       printf("2:value1 %d\n", value1);
       printf("2:value2 %d\n", value2);
   }

   printf("3:value1 %d\n", value1);
   printf("3:value2 %d\n", value2);

   return 0;
}

9~15行目が関数内に別で用意されたブロックになります。
変数の宣言は関数の先頭でのみ。と解説したことがありますが、厳密にはブロックの先頭でのみ宣言が可能ということです。

参考書(P224)の実行結果の通り、9~15行目のブロック内で宣言したvalue1は、5行目のvalue1とは同じ変数名ですが、別の実体ということになります。

このブロックですが、基本的には設計するプログラムで使用することを敬遠するのが一般的です。
理由は、分かり難くくバグの元になりかねない為です。
同じ変数が多数出現したり、関数内で多数ブロックが出てきて変数宣言されたり、というのは、
複雑なプログラムになっていきますので、シンプルな記述を心掛ける方が良いと思われます。

最後まで生き残る変数

これまでは関数内で宣言された変数について解説してきました。
関数内の変数をローカル変数と呼んだりします。

これに対して、関数の外でも有効な範囲を持つ変数をグローバル変数と呼ぶのですが、ここではグローバル変数について解説をしていきますので、参考書のコードを元にしていきます。

#include <stdio.h>

int count;    /* グローバル変数 */

int countfunc(void);

int main(void)
{
    countfunc();
    countfunc();
    countfunc();

    return 0 ;
}

int countfunc(void)
{
   count++;

   printf("%d\n", count);

   return count;
}

これまでは関数内の先頭で変数を宣言していましたが、ここでは3行目にcount変数を宣言しています。
このコードはファイルに書かれているのですが、ファイルの先頭で宣言された変数はグローバル変数として、そのファイル全体で有効な変数となります。

その為、main関数内でもcountという変数に代入をすると3行目のcount変数に反映がされ、
countfunc()関数内でcountに代入やcount++の様にインクリメントを行うと、3行目のcount変数に反映されます。

つまり、このファイル内ではcountという変数に処理を与えると、3行目に宣言されたcountに反映がされるということです。

関数を選ばないので、ファイル内で共通して使用したい変数値を持ちたい場合は、
ファイルの先頭でグローバル変数として宣言しておくと、設計の時に便利に使用することが可能です。

但し、経験を積むとわかるのですが、色んな場所で一つの変数の値を変える処理が点在すると、
いつどこで書き換わったのかが分からなくなり、不具合の原因がどこなのか見失うことに繋がる要因にもなり得ますので、使い方は注意が必要です。

ローカル変数とグローバル変数はどちらが優先か?

同じ名前の変数をグローバルとローカルで用意した場合、どちらが優先処理されるのでしょうか?
例えば参考書のコードを見てみましょう。

#include <stdio.h>

int count;    /* グローバル変数 */

int countfunc(void);

int main(void)
{
    int count;  /* 同名で変数宣言 */

    countfunc();
    count = 10;
    countfunc();
    countfunc();

    printf("main : count = %d\n", count);

    return 0 ;
}

int countfunc(void)
{
   count++;

   printf("%d\n", count);

   return count;
}

3行目がグローバル変数のcount。
9行目がmain関数内 ローカル変数のcount。
どちらが優先されるか?

main関数内の12行目でcount = 10とされたのは、ローカル変数に対して10が代入。
main関数内の16行目のprintf関数で表示されているのは、ローカル変数の値を表示。

countfunc()関数内のcountは、グローバル変数に対してインクリメント。
countfunc()関数内のprintf関数で表示されるのは、グローバル変数の値を表示。

結果、関数内で宣言された変数は関数内でのみ有効であり、関数内ではグローバル変数よりも優先されて扱われる。ということです。

静的なローカル変数

少し複雑な変数の解説になりますが、ローカル変数、グローバル変数の他にもう一つ変数の状態があります。
静的変数と呼ばれるもので、変数宣言時に型の前にstaticという単語を付記して宣言します。

 static int count;

上記の様な記述で宣言します。
使い方のコード例を見てみましょう。

#include <stdio.h>

int countfunc(void);

int main(void)
{
    countfunc();
    countfunc();
    countfunc();

    return 0 ;
}

int countfunc(void)
{
   static int count;   /* 静的なローカル変数 */
   count++;
   printf("%d\n", count);

   return count;
}

これまでの変数の解説では、関数内で宣言された変数はローカル変数と呼び、
関数内の { } のブロックの括り内が有効範囲でした。

ところが、staticと付記された関数内の変数は条件が変わります。
関数内でしか処理できないことは同じです。
上記のコードでは、countfunc()関数内でしか、count変数にアクセスできません。

しかし、countfunc()関数の処理が終わっても、count変数は消えずに残り続けます。
countfunc()関数の return count; を処理しても変数は消滅せず、このファイルに書かれたプログラムが終了するまで変数は有効なままで残ります。

つまり、count変数に格納された値は、main関数の処理が全て実行され終了になるまで記憶され続けることになります。

グローバル変数としてファイルの先頭に count変数を宣言して使用するのも、
関数内でstaticを付記して、静的なローカル変数として宣言して使用するのも、
どちらもmain関数の処理が全て終わるまで有効なままで残ります。

どちらを使用しても良いのですが、静的なローカル変数は宣言された関数内でしか変数にアクセスできないので、他の関数から値を変えられる心配がありません。
グローバルにするほどではないが、一時的に値を記憶しておきたい場合は静的なローカル変数とする意味があります。