2 記憶クラス

2.1 記憶クラスとは

C記憶クラスは,メモリーにデータを格納する方法のことである.C言語には,4個の記憶 クラス指定子(1)auto,(2)extern,(3)register, (4)staticがある.これらは,変数宣言の先頭に
auto int hogehoge;
extern int fugafuga;
register int foo;
static int bar;

と書く.

記憶クラスは4種類あるが,この中でstaticはローカル変数とグローバル変数では, 動作が異なる.したがって,5種類の動作があると考える.それぞれの動作について,次 節以降に説明する.

2.2 記憶クラスの使い方

2.2.1 自動変数(auto)

これは,自動変数と呼ばれるもので,諸君がいままで使ってきた変数である.変数宣言の とき記憶クラス指定子が無い場合,自動変数としてコンパイラーは取り扱う.したがって, プログラム中に
auto int hogehoge;

というようにautoを書くことはまず無い.いままで多くのプログラ ムを見てきたが,わざわざautoを記述しているプログラムを見たことがない.C言語 の前身であるB言語との互換性を保つために,現在では使われないauto がある.

自動変数は,それが宣言されている関数が呼び出されたときに記憶領域--メモリー-- が確保されて,関数での処理が終了したら廃棄される.例えば,リスト1のよう に関数hoge()を3回呼び出すプログラムの動作を考える.関数hoge()の中の変 数aは自動変数である.ゆえに,関数hoge()が呼び出される毎に記憶領域 aが初期化されて作成され,その関数の動作が終了する毎に廃棄が実行される. 自動的に記憶領域の処理を行われるので,「自動変数(auto)」と呼ばれる.

   1 #include <stdio.h>
   2 
   3 int hoge(void);
   4 
   5 //===========================================================
   6 // メイン関数
   7 //===========================================================
   8 int main(void)
   9 {
  10   int i, foo;
  11 
  12   for(i=1; i<=3; i++){
  13     foo = hoge();
  14     printf("%d\tfoo=%d\n", i, foo);
  15   }
  16 
  17   return 0;
  18 }
  19 
  20 //===========================================================
  21 // 自動変数を使ったユーザー定義関数 hoge
  22 //===========================================================
  23 int hoge(void)
  24 {
  25   int a=0;              // 自動変数 auto int a=0 もOK
  26 
  27   a++;                  // a = a+1 と同じ
  28 
  29   return a;
  30 }

このプログラムの実行結果は,以下のようになる.図2に関数呼出と 変数aの変化を示す.関数hoge()内の自動変数aは関数が呼び出された時 に作成され,関数での処理が終わった時点で消滅する.したがって,以下の結果が得られ るのである.



\fbox{実行結果}

1       foo=1
2       foo=1
3       foo=1
図 2: 関数hoge()の動作と内部の自動変数aの変化
\includegraphics[keepaspectratio, scale=1.0]{figure/hoge_auto.eps}

2.2.2 静的変数(static)

記憶クラスに静的変数を用いると,関数の処理が終了しても変数を破棄しないで保存する ことができる.そうすると,次にその関数を呼び出したとき,保存した変数を利用するこ とが可能となる.次のように
static int hogehoge;

と書くことにより,変数hogehogeを静的変数とすることができる.

グローバル変数でも,変数の値を保存することができる.しかし,グローバル変数は他の関数か らもアクセスできるので,思わぬことで値が変更されることがある.したがって,変数の 値を保存する必要があるが,他の関数がそれを使わない場合,静的変数を使う.

静的変数を使ったプログラムをリスト2に示す.このプログラムの25行 目のみ,リスト1と異なる.すなわち, 関数hoge()の変数aが 静的変数になっている.

   1 #include <stdio.h>
   2 
   3 int hoge(void);
   4 
   5 //===========================================================
   6 // メイン関数
   7 //===========================================================
   8 int main(void)
   9 {
  10   int i, foo;
  11 
  12   for(i=1; i<=3; i++){
  13     foo = hoge();
  14     printf("%d\tfoo=%d\n", i, foo);
  15   }
  16 
  17   return 0;
  18 }
  19 
  20 //===========================================================
  21 // 静的変数を使ったユーザー定義関数 hoge
  22 //===========================================================
  23 int hoge(void)
  24 {
  25   static int a=0;       // 静的変数
  26 
  27   a++;                  // a = a+1 と同じ
  28 
  29   return a;
  30 }

このプログラムの実行結果は,以下のようになる.図3に1回目関数呼出と 変数aの変化を示す.関数hoge()内の静的変数aはプログラム実行に先立っ て--メイン関数の実行よりも前に--静的変数aは作成され,初期化(a=0)が行 われる.そして,プログラムが動作している間,その変数は保存される.したがって,以下の結果が得られ るのである.



\fbox{実行結果}

1       foo=1
2       foo=2
3       foo=3
図 3: 関数hoge()の動作と内部の静的変数aの変化
\includegraphics[keepaspectratio, scale=1.0]{figure/hoge_static.eps}

2.2.3 複数のソースファイルと外部変数

2.2.3.1 複数のソースファイルのコンパイル

C言語では,複数のソースファイルからひとつの実行ファイルを作成する仕組みがあり, それを分割コンパイルと言う.これは,複数のプログラマーでひとつのプログラムを作成 するときに便利である.例えば,A君がfile1.cを,B君がfile2.cを,C君がfile3.cを作成 し,それをまとめてひとつの実行ファイルjikkouを作成することができる(図 4).この仕組みは,一人で大規模なプログラムを作成するときにも 使う.ひとつのファイルだと長すぎて,内容がわかり難くなれば,関連 した関数をまとめてひとつにファイルにすることにより,管理が容易になる.
図 4: 複数のソースファイルからひとつの実行ファイルを作る.
\includegraphics[keepaspectratio, scale=1.0]{figure/bunkatu_compile.eps}

2.2.3.2 グローバル変数の問題

複数のソースファイルからひとつの実行ファイルを作成する場合,全てのファイルで使い たいグローバル変数をどこで宣言するか?--という問題がある3.これは,C言語では次のようにすることになっている. -4pt リスト34に分割コンパイルで,共通のグローバル 変数を使った例を示す.グローバル変数fooは,file1.cでもfile2.cでも使用できる 同じ変数である.file2.cでグローバル変数の宣言を行い,file1.cではexternを付 けることにより,どこか他の場所で宣言されていることを示している.


   1 #include <stdio.h>
   2 
   3 extern int foo;       // どこかで宣言されているグローバル変数
   4 
   5 int hoge(void);       // プロトタイプ宣言
   6 
   7 //===========================================================
   8 // メイン関数
   9 //===========================================================
  10 int main(void)
  11 {
  12   int i;
  13 
  14   for(i=1; i<=3; i++){
  15     hoge();
  16     printf("%d\tfoo=%d\n", i, foo);
  17   }
  18 
  19   return 0;
  20 }

   1 #include <stdio.h>
   2 
   3 int foo=0;                 //   グローバルな自動変数
   4 
   5 //===========================================================
   6 // グローバルな自動変数の値を変えるユーザー定義関数 hoge
   7 //===========================================================
   8 void hoge(void)
   9 {
  10 
  11   foo++;                  // foo = foo+1 と同じ
  12 
  13 }

複数のソースファイルから,ひとつの実行ファイルを作成する場合,コンパイルは次のよ うにする.



\fbox{コンパイル方法}

gcc -o jikkou file1.c file2.c

関数hoge()中のaはfile1.cとfile2.cで使えるグローバル変数なので,次の実 行結果が得られることが理解できるであろう.



\fbox{実行結果}

1       foo=1
2       foo=2
3       foo=3

2.2.4 グローバル変数を静的変数と宣言

グローバル変数の仕組みがexternのみだと,名前の衝突の可能性がある.例えば,A 君は,file1.cのみでgvalueと言う変数名を使いたい.しかし,B君とC君はfile2.cと file3.cで共通につかえるグローバル変数gvalueを使いたい.同じ名前を使用するが, 保存する内容が異なる場合を名前の衝突と言う.

これを解決するには,A君のfile1.cでは,

static int gvalue;

とグローバル変数を静的に宣言する.こうすることにより,file.cでのグローバル変数 gvalueは,file1.cのみのグローバル変数と制限ができる.もちろん,file2.cや file3.cでは
int gvalue;
extern int gvalue;

と宣言する.

グローバル変数を静的に宣言した例をリスト6の3行目に示す. この変数fooとリスト5の3行目のfooは名前が同一で も全く異なるものである.リストリスト56からひとつの実行ファイルを作成しても,2つのfooが保 存する内容は別物である.

   1 #include <stdio.h>
   2 
   3 static int foo=5;       // 静的なグローバル変数
   4 
   5 int hoge(void);         // プロトタイプ宣言
   6 
   7 //===========================================================
   8 // メイン関数
   9 //===========================================================
  10 int main(void)
  11 {
  12   int i;
  13 
  14   for(i=1; i<=3; i++){
  15     hoge();
  16     printf("%d\tfoo=%d\n", i, foo);
  17   }
  18 
  19   return 0;
  20 }

   1 #include <stdio.h>
   2 
   3 static int foo=0;                 //   グローバルな静的変数
   4 
   5 //===========================================================
   6 // グローバルな自動変数の値を変えるユーザー定義関数 hoge
   7 //===========================================================
   8 void hoge(void)
   9 {
  10 
  11   foo++;                  // foo = foo+1 と同じ
  12 
  13 }

静的なグローバル変数が理解できたならば,次の実行結果は納得できるであろう.リスト 5の16行目のfooは,file1.cのグローバル変数のfoo のことである.



\fbox{実行結果}

1       foo=5
2       foo=5
3       foo=5

2.2.5 レジスター変数

2.2.5.1 コンピューターの仕組み

コンピューターは,図5のようになっている.メモリはデータやプログ ラムを記憶する装置である.そして,CPUはデータを処理--計算(演算)--する装置であ る.このメモリーとCPUの間で猛烈な勢いで,データの受渡しを行うことにより,情報を 処理する.CPUの中には,演算装置とレジスターがある.CPUがメモリーから受け取るデー タは,いったんレジスターに格納する.レジスターに格納したデータを演算装置が処理す るのである.レジスターとメモリーは,どちらもデータを格納するが,以下のような違い がある. レジスターはCPUの中にある小さいメモリーのことである.

図 5: コンピューターの原理的な仕組み.矢印はデータの流れを表す.
\includegraphics[keepaspectratio, scale=0.8]{figure/computer.eps}

2.2.5.2 レジスター

レジスター変数を使えば,メモリーを使うより高速に計算ができる.レジスター変数は
register int rgsval;

と宣言する.これにより,変数regvalはメモリーではなく,レジスターに確保でき る.ただ,レジスター変数は多くないので,それによる計算の高速化には限界がある.も し,使用できる以上のレジスター変数を宣言した場合,メモリーに変数を割り当てるよう にコンパイラーが勝手に実行ファイルを作成する.

一般に,アクセスの多い変数をレジスターに割り付ける.例えば,ループ文の制御変数で ある.リスト7の例では,制御変数のij,計算結果を格 納する変数kにレジスター変数を割り当てている.


   1 #include <stdio.h>
   2 
   3 //===========================================================
   4 // メイン関数
   5 //===========================================================
   6 int main(void)
   7 {
   8   register int i, j, k=0;
   9 
  10   for(i=1; i<=10000; i++){
  11     for(j=1; j<=10000; j++){
  12       k=i+j;
  13     }
  14   }
  15 
  16   return 0;
  17 }

次のようにすると,プログラムの実行時間が分かる.ただし,リスト 7をコンパイルした実行ファイルをtest_regとする.



\fbox{実行時間を計測する方法}

	time ./test_reg

実行結果は,次のようになる.最初のrealが実際にプログラムの実行に要した時間である.



\fbox{実行結果}

real    0m0.110s
user    0m0.108s
sys     0m0.000s

レジスター変数を使わない場合,realの時間は0.227秒であった.レジスター変数を使う と倍の速度で計算できることが分かる.


ホームページ: Yamamoto's laboratory
著者: 山本昌志
Yamamoto Masashi
平成18年11月17日


no counter