記憶クラスと変数の初期設定

| 記憶クラス | auto記憶クラス指定子 | static記憶クラス指定子 | 初期設定 | 演習問題 |

記憶クラス

記憶クラスとはこれから説明していくように, メモリ上で変数や関数などのオブジェクトを管理する方法の分類であり, その生存期間,有効範囲,宣言と定義の区別などを定めます. プログラムで記憶クラスを指定する主な記憶クラス指定子には autoとstaticおよびexternがあります.

文法上はそのほかにregisterとtypedefがありますが registerは効率や細かな制約を除けばautoと同様であり, typedefは新しい型を指定するという 異なる目的のために用いるのでここでは説明を省きます.

本章ではこのうちautoとstaticについて説明をし,externは 次章で説明します. これらの記憶クラス指定子を使う場合は, 次のように変数の定義や宣言の先頭に置きます.

auto int a;
static double x;
extern char c;

これは局所変数でも大域変数でも同じです.

ページ先頭へ戻る

auto記憶クラス指定子

auto記憶クラス指定子をつけて定義される変数はかならず局所変数です. 大域変数にauto記憶クラス指定子がつくことはありません. autoをつけて定義された局所変数は自動変数あるいは auto変数と呼ばれます. ただしこの変数はもっとも頻繁に用いられるため次のようなルールがあります. それはブロック内で記憶クラス指定子を 何も付けずに定義された変数は自動変数とするというものです. よって,これまでブロック内で次のように定義してきた 局所変数はすべて自動変数になります.

int a;
double x;

明示的に自動変数であることを示したい場合は 次のようにauto記憶クラス指定子をつけることになります.

auto int a;
auto double x;

しかし,上のルールがあるのでまず用いる必要はありません.

自動変数は実行中にその変数のブロックに入ったときに メモリ上に領域が確保されます.そして そのブロックから出るときに 領域が解放されます. すなわちそのメモリ領域は別の用途に再利用できるようになります.

関数ブロックの局所変数であれば,関数が呼び出されたときに メモリ上に値を収める場所が確保されて,関数が終了するときに その場所が解放されます.すなわち, 同じ関数が複数回呼び出されるような場合には 呼び出されるたびにメモリの確保と解放を繰り返します.

#include <stdio.h>
#define NUMBER 5

int twice(int a)
{
  int x;          // 自動変数x

  x = a * 2;
  return x;
}

int main(void)
{
  int y;

  y=NUMBER;
  y=twice(y);        // 1回目のxの確保と解放
  y=twice(y);        // 2回目のxの確保と解放
  printf("answer=%d\n", y);
  return 0;
}

また関数の実行中に同じ関数が再帰的に呼び出される場合には, 呼び出されるたびにそれぞれ別のメモリ領域が確保されます.

#include <stdio.h>
#define NUMBER 5

int fact(int a)
{
  int x;          // 自動変数x

  if (a > 1) {
    x = fact(a-1);
    return a * x;
  } else {
    return 1;
  }
}

int main(void)
{
  int y;

  y=NUMBER;
  printf("%d! = %d\n", y, fact(y)); // xの確保が5回,解放が5回起こる.
  return 0;
}

プログラムが実行されるときのメモリ領域の使われ方には いくつかの種類があり,自動変数が確保されるメモリ領域では, 上のようにプログラムの実行が始まってから必要に応じて確保され (これを動的なメモリ管理と呼びます), 後から確保した領域が先に解放されるように使われます. このような使われ方をされるメモリ領域は スタック領域と呼ばれます.

メモリ領域の使われ方には他に,大域変数や 次節のstatic記憶クラス指定子で用いられる静的領域と mallocやfreeなどの標準ライブラリを通じて使うことのできる ヒープ領域があります.これらについては それぞれの章や節で詳しく説明することにしましょう.

ページ先頭へ戻る

static記憶クラス指定子

static記憶クラス指定子は局所変数の定義にも大域変数の定義にも 付けることが出来ますが,両者の意味はまったく異なりますので注意してください.

static局所変数

上で説明したように自動変数はブロックに出入りするごとに メモリ領域の確保と解放を繰り返します.これは ブロックを超えて変数の値を保持することが出来ないことを意味します. これに対してstatic記憶クラス指定子をつけて定義された局所変数 (これをstatic局所変数と呼ぶことにしましょう)は プログラムの実行開始時から変数の領域が確保されており, プログラムの終了時に初めて解放されます. すなわち局所変数であるにもかかわらず, ブロックの出入りに関係なくプログラムの最初から最後までずっと同じ 領域を使い続けることが出来ます.

#include <stdio.h>

int total(int a)
{
  static int t = 0;     // static局所変数

  t = t + a;
  printf("total=%d\n", t);
}

int main(void)
{
  total(1);             // total=1と出力
  total(2);             // total=3と出力
  total(3);             // total=6と出力
  return 0;
}

このようなstatic局所変数が置かれるメモリ領域は 先のスタック領域とは異なったメモリ管理が行われます.すなわち, プログラムそれ自体がプログラムの開始時にメモリ上のある領域に置かれ 終了時にその領域が解放されるのと同じようなタイミングで, static局所変数の領域の確保と解放が行われます (これは静的なメモリ管理と呼ばれます). ただしstatic局所変数の領域はプログラムの領域とは異なり 実行中にプログラムから書き換えることが出来ます. このような使われ方をするメモリ領域は静的領域と呼ばれます. ちなみにプログラム自体が置かれるメモリ領域は プログラム領域と呼ばれます.

static大域変数

大域変数にstatic記憶クラス指定子が付いている場合と付いていない場合の違いは 単一のファイルでプログラムを作成している場合には現れず, 複数のファイルからなるプログラムを作成する場合に初めて現れます. よって詳細は次章で説明することにしますが, 大まかには次のようになります.

  1. staticの付いていない大域変数(外部リンケージを持つ大域変数)は, 適切にextern記憶クラス指定子を用いて宣言を行うことによって 他のすべてのファイルに書かれた関数から参照することが可能です.
  2. staticの付いている大域変数(内部リンケージを持つ大域変数)は, その変数が定義されたファイルに書かれた関数からしか参照することが 出来ません.

従って例えば,複数のファイルで定義された 同じ名前のstatic付き大域変数は別の変数として扱われます.

なお,上の1と2のどちらの大域変数も, メモリ上ではstatic局所変数と同様に 静的領域に確保されて静的なメモリ管理が行われます.

ページ先頭へ戻る

初期設定

変数を定義するときには初期設定を行うことが出来ます. 例えば次のように初期設定できます.

int x=100;
int y=x+100;
static int i=0;

すなわち初期設定の構文は次のようになります.

データ型 変数名 = 初期値式;
記憶クラス指定子 データ型 変数名 = 初期値式;

従って上の記憶クラスの節の例ではわざわざ

int y;
y=5;

と分けていたのですが,次のようにひとまとめにすることが出来ます.

int y=5;

次に初期設定が行われるタイミングですが, これは変数の種類によって2通りあります.

  1. 自動変数は定義されているブロックに入るごとに初期設定されます.
  2. 大域変数とstatic局所変数はプログラムの実行開始時に一度だけ行われます.

実際には実行効率を上げるために,ほとんどの場合 2の初期設定はコンパイル時にすでに行われています. (2の変数はプログラムの開始時にプログラムそれ自体と 同じようなタイミングでメモリ上に置かれることを思い出してください).

このために細かいことをいえば2の初期設定で 初期値式に使える式には多少制限があります. 2の場合はコンパイル時にその値があらかじめ計算して定まるような式でないと いけません.一方,1の場合は実行時にブロックに入るたびに 値が異なるような式でも構いません.

さらに,変数の初期設定が行われていない場合の初期値については, これも変数の種類によって2通りに分かれます.

  1. 自動変数では初期設定されないのでその値は不定です.
  2. 大域変数とstatic局所変数では暗黙に 0(すべてのビットが0の値) に初期設定されます.

従ってstatic局所変数の節で挙げたプログラムでは, 実際にはstatic局所変数tの初期設定をしなくても正しく動作します.

ページ先頭へ戻る

演習問題

A. 以下の二つのプログラムの実行結果を予想し,なぜそうなるか説明しなさい. そして実際に実行してその予想と合っているか確認しなさい.

#include <stdio.h>

int f(void)
{
  int x=0;

  x++;
  return x;
}

int main(void)
{
  printf("answer=%d\n", f());
  printf("answer=%d\n", f());
  printf("answer=%d\n", f());
  return 0;
}
#include <stdio.h>

int f(void)
{
  static int x=0;

  x++;
  return x;
}

int main(void)
{
  printf("answer=%d\n", f());
  printf("answer=%d\n", f());
  printf("answer=%d\n", f());
  return 0;
}

B. 次のプログラムを変更して,関数factが呼び出されるたびに それが何回目の呼び出しかを表示するようにしなさい. ただし回数を数えるのにstatic局所変数を用いること.

#include <stdio.h>
#define NUMBER 5

int fact(int a)
{
  if (a > 1)
    return a * fact(a-1);
  else
    return 1;
}

int main(void)
{
  int x=NUMBER;

  printf("%d! = %d\n", x, fact(x));
  return 0;
}

表示は例えば以下のようになる.

count = 1
count = 2
count = 3
count = 4
count = 5
5! = 120

ページ先頭へ戻る