コンピューターは複雑な装置であるが、図
1のような機能の
集まりに分解できる
2。それぞれの機能(装置)は、制御信号線とデータ信号線で
接続されている。そこを、0と1のパルスの信号が信じられないくらい高速でかつ調和を取って流れ、全体としてコンピューターが動作するのである。大量の信号が一つも間違いなく伝送されるのは驚きであ
る。この信号線は人間で言えば神経に当たり、そこに流れる内容(情報)は、
- 制御信号
- 「・・・しなさい」とハードウェアーに動作の指示をする信号
- データ信号
- 「・・・を」とデータや命令の内容を示す信号
である。
コンピューターの基本構成は図1に示したとおりであるが、
制御装置と演算装置、記憶装置、入力装置、
出力装置を五大装置と呼ぶ。それぞれは、次のような働きがある。
- 制御装置
- 主記憶装置(メインメモリー)に格納されているプログラム(命
令)を受け取り、それを解読し、電気信号に変え、各装置に指
令を出す。
- 演算装置
- 主記憶装置に格納されているデータを受け取り、制御装置の
指令に従い、それを加工(処理)する。
- 記憶装置
- 記憶装置は、主記憶装置と補助記憶装置がある。
- 主記憶装置
- 命令とデータからなるプログラムを格納する。
- 補助記憶装置
- 主記憶装置には容量に制限があるので、それを越えるものをここ
に格納する。また、主記憶装置は電源を切ると、データが失われる
ので、半永久的に残したい場合、補助記憶装置に格納する。ハード
ディスクやCD-ROMがこれに当たる。
- 入力装置
- コンピューターの外部からデータを取り込む装置である。キー
ボードやマウス等がこれに当たる。
- 出力装置
- 主記憶装置に格納されているデータをコンピューター外部に
出力する装置である。ディスプレイやプリンター等がこれに当
たる。
モデムやLANカードのように、入出力装置にもなっているものがあるので注意が必要であ
る。
これらのなかで、制御装置と演算装置をまとめて中央制御装置(CPU:Central Processing
Unit)と言う。これを一つのチップにまとめたものを MPU(Micro Processing Unit) と言
うことになっている。ただ、MPUとCPUはほとんど同義語として使われるので、本講義では
全て CPU に統一する。その方が諸君もなじみ深いであろう。
ここで、コンピューターを構成する最小の部品を考える。そうするとCPUとメインメモリー
があれば良いことが分かる。これでも、メインメモリーにプログラムを格納して、CPUと
データの受け渡しを行い、データを処理することができる。図
2のよ
うなものである。事実、CPUと入出力装置の間に流れるデータは、メインメモリーを介し
ている。従って、コンピューターの原理的なモデルを図
2のように考
えても良いだろう。
プログラマーはメモリーの内容について、ある程度自由に変更ができる。そのようなこと
から、メモリーを意識してプログラムを作成することが重要である。アセンブラー言語を使うと
なるとCPUについても意識が必要であろうが、C言語ではそこまで要求しない。
本日のメインテーマはポインターであるが、それを深く理解するために、メモリーの知識
が必要である。しかし、そんなに難しいことではない。
2.3 メインメモリーのモデル
メインメモリーのハードウェアーの構造については、ここではどうでも良い。それよりか、
メインメモリーのモデルを理解することが重要である。
メインメモリーの役目は、命令とデータからなるプログラムを記憶することである。そのプロ
グラムは全て、0と1の数字で表せ、2進数で表現可能である。どのようなモデルでこの2進
数が格納されているか学習する。
プログラムという情報は記憶するだけでは全く役に立たない。記憶した内容を取り出せて
初めて、活用ができる。そこで、メモリーは記憶するための住所が決められている。この
住所のことをアドレス(address)と言い、0から整数の番地がふってある。諸君が使ってい
るパソコンのアドレスは32ビットで表現されている3。そして、一つの番地には、8個の0と1が記憶
できる。この様子を図3に示す。
図を見て分かるとおり、2進数の表現は桁数が多くて人間にとって大変である4。そこで、通常は、2進数の4桁をまとめて、16進数で
表す。そうすると、アドレスは16進数8桁、記憶内容は16進数2桁で表すことができ、分か
りやすくなる。その様子を図4に示す。
ついでに述べておくが、1個の0あるいは1の情報量を1ビットと言う。8ビットで1バイトと
言う。従って、メインメモリーの一つの番地(アドレス)には、1バイト(8ビット)の情報が
記憶できる。
メモリーについて覚えておくことは、以下の通りである。
- アドレスは32ビットで表現している。これは16進数では8桁である。
- 一つのアドレスに8ビット(1バイト)記憶できる。
- 8ビットを1バイトと言う。
諸君が使う変数の型は、文字型と整数型、倍精度実数型がほとんどである。秋田高専内で
使用されているパソコンのそれぞれのサイズは表
1の通りであ
る。文字型であれば、一つのアドレス内に格納することができるが、整数型では4つ、
倍精度実数型では8個のアドレスが必要である。一つのアドレスに一つのデータが記憶さ
えれている訳ではない。付録のリスト
3にデータ型のバイト数を調べ
るプログラムを載せておく。
表 1:
変数の型とバイト数
型名 |
データ型 |
バイト数 |
ビット数 |
文字型 |
char |
1 |
8 |
整数型 |
int |
4 |
32 |
倍精度実数 |
double |
8 |
64 |
それでは、実際にメモリーにデータが格納する様子を見よう。次のようにプログラムに書
いたとする。
double x=-7.696151733398438e-4;
int i=55;
char a='a';
それぞれのデータは、
- xの値のビットパターンは、前回の講義の付録を見よ。
-
からiのビットパターンは分かる。
- 文字の'a'はアスキーコードのである。
となっている。実際にこれを確かめるプログラムを、付録のリスト
4
に示している。このプログラムを私のパソコンで実行させると、図
5のようなメモリー配置になっていることが分かっ
た。表
1の通り、整数型と実数型は複数のアドレスにわたって
データが格納されていることが分かる。
鋭い学生は、データの並びが逆であることが分かるであろう。例えば、整数のiであ
るが、
なので、
と並ぶと考えられるが、実際は図
5の通り逆である。これはCPUがそのように作られ
ているからである。このように逆に配置させる方法をリトルエンディアンと言
う。Intel社のCPUはリトルエンディアンである。一方、そのままのメモリーに配置する方
法はビッグエンディアンと呼ばれる。このようにメモリーにデータを並べる方
法は2通りあって、それをバイトオーダーと言う。
ここで、理解しておくべきことは、以下の通りである。
- 必要なバイト数のメモリー領域を使いデータは格納される。
これまでは、メモリーのデータの格納方法を学習した。以前、プログラム
(命令とデータ)は全てメモリーに格納されると述べた。ここでは、もう少し進んで、プロ
グラムがメモリーの中にどのように格納されているか調べてみよう。この辺のことと、マ
シン語が分かると、ハッカー(クラッカーと言った方が適切かも)になれるかも・・・。
それでは、リスト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 で整数を返すことを示している。仮引
数は、整数型の i と j である。
- 26行
- 変数 i の先頭アドレスを表示。
- 27行
- 変数 j の先頭アドレスを表示。
- 29行
- 関数funcの終了を表し、呼び出し元(ここではmain関数)に整数の
i と j の積を返している。
- 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 }
--- address -----------
main 0x8048368
func 0x80483eb
main-i 0xbffff6b4
func-i 0xbffff690
func-j 0xbffff694
実行結果から、命令とデータは図
6のようになっ
ていることが分かるであろう。命令である関数は、大体近くのメモリ上に配置されている。
しかし、データの内容を格納する変数は、ずっと離れてところにメモリーが割り当てられ
ている。
関数の中で宣言される変数は、ローカル変数と言い、その宣言した関数でのみアクセスが
可能である。従って、同じ名前であるが、違う関数で宣言されたローカル変数は全く別物であ
る。図6で分かるように、関数mainと関数
funcで同じ名前のローカル変数 i を宣言してるがメモリー上の配置は全く異
なる。このことからも、名前は同じであるが、全く違うものであることが理解できる。
ここで、理解しておくべきことは、以下の通りである。
- プログラムは命令とデータから構成され、いずれもメモリーの中に格納される。
- プログラムの関数(これが命令)が格納されるアドレスは、関数名で参照できる。
- データが格納されるアドレスは、変数名の前に & を付けることで参照で
きる。& はアドレス演算子である。
- アドレスの表示には変換指定子 %pを使う。
- ローカル変数は名前が同じでも、メモリーの配置場所は異なる。正確言うと、そ
の関数が呼び出されたときのみ、ローカル変数はメモリーに割り当てられる。
図 6:
プログラムのメモリーへの格納。記憶の内容は不明なので、??としてい
る。
|
ホームページ:
Yamamoto's laboratory著者:
山本昌志
Yamamoto Masashi
平成19年6月24日