2 メモリー

今まで学習したC言語の内容は,FORTRAN2と置き換えが可能でる.対応するFORTRANの命令がある.C言語のプロ グラムは命令の書き換えのみで,FORTRANのプログラムになる.しかし,ここで説明する ポインター(pointer)はFORTRANにない機能である.これこそ,CとFORTRANの大きな違いで, C言語のもっとも大きな特徴となっている.少しばかり難しくので,ここで挫折する人も 多くいる.しかし,その内容を理解すれば,ポインターなんか難しくないはずである.

メモリーが分からないと,ポインターは理解できない.そこで,ポインターの説明の前に, コンピューターのメモリーについて,説明する.ただし,諸君は,アセンブラ言語を既に 学習しているので,この辺りのことはある程度理解していると思う.

2.1 メモリーとCPUの関係

ここで,コンピューターを構成する最小の部品を考える.そうするとCPUとメインメモリー があれば良いことが分かる.これでも,メインメモリーにプログラムを格納して,CPUと データの受け渡しを行い,データを処理することができる.図1のよ うなものである.事実,CPUと入出力装置の間に流れるデータは,メインメモリーを介し ている.従って,コンピューターの原理的なモデルを図1のように考 えても良いだろう.

プログラマーはメモリーの内容について,ある程度自由に変更ができる.そのようなこと から,メモリーを意識してプログラムを作成することが重要である.アセンブラー言語を使うと なるとCPUについても意識が必要であろうが,C言語ではそこまで要求しない.

図 1: もっとも原始的なコンピューター
\includegraphics[keepaspectratio, scale=1.0]{figure/CPU_memory.eps}


2.1.1 メインメモリーのモデル

メインメモリーのハードウェアーの構造については,ここではどうでも良い.それよりか, メインメモリーのモデルを理解することが重要である.

メインメモリーの役目は,命令とデータからなるプログラムを記憶することである.そのプロ グラムは全て,0と1の数字で表せ,2進数で表現可能である.どのようなモデルでこの2進 数が格納されているか学習する.

プログラムという情報は記憶するだけでは全く役に立たない.記憶した内容を取り出せて 初めて,活用ができる.そこで,メモリーは記憶するための住所が決められている.この 住所のことをアドレス(address)と言い,0から整数の番地がふってある.諸君が使ってい るパソコンのアドレスは32ビットで表現されている3.そして,一つの番地には,8個の0と1が記憶 できる.この様子を図2に示す.

図を見て分かるとおり,2進数の表現は桁数が多くて人間にとって大変である4.そこで,通常は,2進数の4桁をまとめて,16進数で 表す.そうすると,アドレスは16進数8桁,記憶内容は16進数2桁で表すことができ,分か りやすくなる.その様子を図3に示す.

ついでに述べておくが,1個の0あるいは1の情報量を1ビットと言う.8ビットで1バイトと 言う.従って,メインメモリーの一つの番地(アドレス)には,1バイト(8ビット)の情報が 記憶できる.

メモリーについて覚えておくことは,以下の通りである.

図 2: メモリーのモデル(2進数)
\includegraphics[keepaspectratio, scale=1.0]{figure/memory_binary.eps}
図 3: メモリーのモデル(16進数)
\includegraphics[keepaspectratio, scale=1.0]{figure/memory_hexadecimal.eps}

2.2 データの型とバイト数

諸君が使う変数の型は,文字型と整数型,倍精度実数型がほとんどである.秋田高専内で 使用されているパソコンのそれぞれのサイズは表1の通りであ る.文字型であれば,一つのアドレス内に格納することができるが,整数型では4つ, 倍精度実数型では8個のアドレスが必要である.一つのアドレスに一つのデータが記憶さ えれている訳ではない.sizeof(型)演算子(教科書p.126)を使うと,型が必要とするバイト数がわか る.


表 1: 変数の型とバイト数
型名 データ型 バイト数 ビット数
文字型 char 1 8
整数型 int 4 32
倍精度実数 double 8 64

それでは,実際にメモリーにデータが格納する様子を見よう.次のようにプログラムに書 いたとする.

  double x=-7.696151733398438e-4;
  int i=55;
  char a='a';
それぞれのデータは, となっている.実際にこれを確かめるプログラムを,付録のリスト4 に示している.このプログラムを私のパソコンで実行させると,図 4のようなメモリー配置になっていることが分かっ た.表1の通り,整数型と実数型は複数のアドレスにわたって データが格納されていることが分かる.

鋭い学生は,データの並びが逆であることが分かるであろう.例えば,整数のiであ るが, $ (55)_{10}=(00000037)_{16}$なので, $ 00\rightarrow 00\rightarrow
00\rightarrow 37$と並ぶと考えられるが,実際は図 4の通り逆である.これはCPUがそのように作られ ているからである.このように逆に配置させる方法をリトルエンディアンと言 う.Intel社のCPUはリトルエンディアンである.一方,そのままのメモリーに配置する方 法はビッグエンディアンと呼ばれる.このようにメモリーにデータを並べる方 法は2通りあって,それをバイトオーダーと言う.

ここで,理解しておくべきことは,以下の通りである.

図 4: メモリー中に格納されたデータの例
\includegraphics[keepaspectratio, scale=1.0]{figure/data_in_memory_hexadecimal.eps}

2.3 プログラムが格納される様子

これまでは,メモリーのデータの格納方法を学習した.以前,プログラム (命令とデータ)は全てメモリーに格納されると述べた.ここでは,もう少し進んで,プロ グラムがメモリーの中にどのように格納されているか調べてみよう.この辺のことと,マ シン語が分かると,ハッカー(クラッカーと言った方が適切かも)になれるかも・・・.

それでは,リスト1に示す簡単なプログラムで,データとメモリー の格納アドレスを調べてみよう.このプログラムの内容は,以下の通りである.まだ,詳 細は分からなくても良いが,大体の流れをつかんで欲しい.

1行
今のところおまじない
2行
関数funcのプロトタイプ宣言
4-6行
コメント文.プログラムの動作には無関係.プログラマーのために記述.
7行
main 関数の始まり.int で整数を返すことを示し,void で引数が 無いことを示している.
9行
整数変数 i の宣言
11行
結果を分かりやすくするために -- address ------ を表示.最後 に \n で改行.
12行
main 関数が書かれている先頭アドレスを表示.関数名はアドレスを表 しており,変換指定子 %p でディスプレイに表示. \t は,タブを表し,適当な空白が入る.
13行
関数 func が書かれている先頭アドレスを表示.
14行
変数 i の先頭アドレスを表示.変数名に & を付けると,その先頭 アドレスを示すことになる.& はアドレス演算子である.
16行
関数 func に処理が移り,その戻り値を変数 i に代入.
18行
main関数の終了を表し,呼び出し元(OS)に整数の 0 を返している.
19行
main関数のブロックの終わり.
21-23行
コメント文
24行
関数 func の始まり.int で整数を返すことを示している.仮引 数は,整数型の ij である.
26行
変数 i の先頭アドレスを表示.
27行
変数 j の先頭アドレスを表示.
29行
関数funcの終了を表し,呼び出し元(ここではmain関数)に整数の ij の積を返している.
31行
関数funcのブロックの終わり.

   1 #include <stdio.h>
   2 int func(int i, int j);
   3 
   4 /*======================================================*/
   5 /*   メイン関数                                          */
   6 /*======================================================*/
   7 int main(void){
   8 
   9   int i;
  10 
  11   printf("--- address -----------\n");
  12   printf("\tmain\t%p\n", main);
  13   printf("\tfunc\t%p\n", func);
  14   printf("\tmain-i\t%p\n",&i);
  15 
  16   i=func(5,3);
  17 
  18   return 0;
  19 }
  20 
  21 /*======================================================*/
  22 /*  func 関数                                           */
  23 /*======================================================*/
  24 int func(int i, int j){
  25 
  26   printf("\tfunc-i\t%p\n",&i);
  27   printf("\tfunc-j\t%p\n",&j);
  28 
  29   return i*j;
  30 
  31 }
\fbox{実行結果}
	--- address -----------
	        main    0x8048368
	        func    0x80483eb
	        main-i  0xbffff6b4
	        func-i  0xbffff690
	        func-j  0xbffff694
実行結果から,命令とデータは図5のようになっ ていることが分かるであろう.命令である関数は,大体近くのメモリ上に配置されている. しかし,データの内容を格納する変数は,ずっと離れてところにメモリーが割り当てられ ている.

関数の中で宣言される変数は,ローカル変数と言い,その宣言した関数でのみアクセスが 可能である.従って,同じ名前であるが,違う関数で宣言されたローカル変数は全く別物であ る.図5で分かるように,関数mainと関数 funcで同じ名前のローカル変数 i を宣言してるがメモリー上の配置は全く異 なる.このことからも,名前は同じであるが,全く違うものであることが理解できる.

ここで,理解しておくべきことは,以下の通りである.

図 5: プログラムのメモリーへの格納.記憶の内容は不明なので,??としてい る.
\includegraphics[keepaspectratio, scale=1.0]{figure/program_in_memory_hexadecimal.eps}

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


no counter