ハードを知らねばソフトは書けない,
コンピュータの中身を楽しく学ぼう

講師 矢沢 久雄

本セミナーは,全5回にわたって,「プログラムがなぜ動くか」を“楽しく”解説する。基礎的なお勉強の話はつまらなくなりがちだが,本来この仕組みは楽しく面白いものである。プログラミングの経験がある読者もない読者も,このセミナーを楽しんでもらえれば,プログラムの書き方が変わるはずだ。

 「私はソフトウエアが専門なので,ハードウエアのことはよく分かりません」などと言うプログラマがたまにいる。まったく困ったものだ。そんなプログラマに限って,ろくなプログラムが書けず,つまらないバグを抱えたまま途方に暮れていがちである。

 いきなり失礼なことを書いてしまったかもしれない。しかし,これは事実である。プログラムとは,コンピュータのハードウエアを制御する命令である。全くハードウエアが分からないのでは,そもそもプログラミングなどできないはずだ。

 すべてのプログラマがハードウエアのすべてを知るべきだ,と主張したい訳ではない。プログラミングが主な業務であるなら,プログラミングにかかわる基礎的なハードウエアの知識だけをマスターしておけば十分である。ハードウエアの上で動作するOSやアプリケーション,あるいはプログラミング言語そのものに対しても同様である。つまり,“なぜプログラムが動くか”を知ることだ。

 本セミナーは,この,なぜプログラムが動くかを“やさしく,楽しく”学んでもらうことを目的とする。「そんなことは知らなくてもプログラムは書けるからいいや」とは思わないでほしい。なぜ動くかを知ることで,あなたのプログラムの書き方は必ず変わるはずである。

ハードを知らなければソフトは書けない

 筆者は常々,プログラミングの学習手順は外国語のそれに似ていると感じている。外国語の学習手順では,まず初めに基本的な「単語や文法」を学び,次に様々な「慣用句」を学び,最後に本格的な「長文読解や作文」を学ぶ。これらをプログラミングの学習手順にあてはめると,単語や文法が「プログラミング」,慣用句が「アルゴリズム」,長文読解や作文が「システム設計」となる。

 外国人を良く知ることが外国語で意思の疎通を取る上で重要なのは言うまでもないだろう。プログラミングについても全く同じである。

 アルゴリズムとシステム設計の学習は,本誌の過去のセミナーの中で取り上げてきた*1。順序は前後してしまったが,このセミナーでは,プログラミングの学習を3回に分けて解説していく予定である。第1回ではプログラミングのABCに相当するハードウエアと2進数を解説する。第2回ではプログラミングの手法,入出力や演算,変数や定数について,第3回ではOSとアプリケーションの役割を学ぶ。全3回のセミナーの狙いは,読者にプログラムがなぜ動くかを楽しく理解していただき,それによってよりよいプログラムを書けるようになっていただくことだ*2

 このセミナーと過去のセミナーとを合わせて,企業の新人研修などの教材として活用していただければ幸いである。すでに十分な経験のある読者でも,あらためてプログラミングの基礎を復習でき,そこには新たな発見もあることだろう*3

まずは箱の中をのぞいてみる

 コンピュータには,家庭用電化製品を制御するマイコンから世界に数台しかないスーパー・コンピュータまで様々な種類があるが,ここでは最も一般的に利用されているパソコンに限定して,ハードウエアの基礎を説明しよう。あなたが普段書いているソフトウエアが,これらのハードウエアをどのように利用しているかを,ちょっとだけ気にしながら読んでほしい。

 パソコンのきょう体を開けて中をのぞいて見ると,多くのピンを持つ黒い部品がいくつか目つくはずである。これはご存知ICである。多くのピンを持ったムカデのようなその姿に恐怖を感じるかもしれないが,それほど心配する必要はない。ICを取り扱うのは,思いのほか簡単なのだ。

 ちょっと例えを利用してみよう。あなたはアンプやラジオ・チューナ,CDプレーヤなどを組み合わせてステレオを設置した経験があるだろうか。それぞれのコンポーネントに電源を接続し,コンポーネントとコンポーネントの間を信号線などで接続する。アンプやCDプレーヤなどそれぞれの中身は知らなくても,ステレオを設置することはできるだろう。

 コンピュータも同じである。ICのピンには,必ず電源を供給するためのピンが最低2本ある。すべてのICは,独立した機能を持った装置であり,ぞれぞれに電源を接続しなければならない。それぞれの装置の内部がどのような仕組みになっているかをすべて理解する必要はない。電源ピンとその他のピンをICの間で接続すれば,コンピュータは完成する*4

演算,制御,入力,出力,記憶

 CDプレーヤとアンプとスピーカを組み合わせれば,CDを聞くステレオを構築することができる。これにラジオ・チューナを加えれば,ラジオ放送も聞くことができるだろう。それぞれ必要な機能を組み合わせることで,目的とするシステムを構築できる。では,コンピュータを作るためには,どのような機能が必要なのだろうか。

図1●コンピュータの五大要素と周辺装置
コンピュータを構成する要素には,演算,制御,入力,出力,記憶の5つがある。これらの機能を複数の部品が分担して備える
 もったいぶらずに答えを書いてしまおう(図1[拡大表示])。コンピュータを作成するために必要とされるICの種類は,CPU,メモリー,およびI/Oの3つである。それぞれの部品が受け持つ機能は,CPUが「演算」と「制御」,メモリーが「記憶」,I/Oが「入力」と「出力」だ。これら5つの要素は「コンピュータの五大要素」と呼ばれる。コンピュータを構成するために必要不可欠の要素で,古くはCharles Babbageの解析エンジン*5の時代から,現在のスーパー・コンピュータまで不変の要素である。

 コンピュータの五大要素に含まれない装置は,「周辺装置」と呼ばれる。キーボード,マウス,ディスプレイ,プリンタ,およびフロッピ・ディスク・ドライブやハード・ディスク・ドライブなどは周辺装置である。周辺装置の中で,データを記憶する機能を持ったディスク装置全般を特に「補助記憶装置」と呼ぶ*6。これらは,コンピュータ本体の構成要素ではない。

 周辺装置は,その種類ごとに専用のI/Oでコンピュータと接続される。コンピュータ本体にI/Oが装備されていない周辺装置を使う場合には,周辺装置専用の「拡張ボード」をコンピュータに増設することになる。例えば,周辺装置として新たにSCSI仕様のハード・ディスク・ドライブを接続したいなら,SCSI装置専用の拡張ボードを増設する。この拡張ボードには,SCSI装置用のI/Oが装備されているのだ。

メモリーやI/Oとの入出力

 さて,多くのパソコンでは,1台に1つCPUを備えているが,メモリーやI/Oが1つだけということはあり得ない。したがって,CPUがメモリーやI/Oとデータを入出力するためには,多くのメモリーやI/Oの中から特定のものを指定する仕組みが必要となる。これを「アドレス」と呼ぶ。

 メモリーやI/Oには,それらがCPUとデータを入出力する単位*7でアドレスが割り振られている。アドレスとは,0からはじまる連続した整数値である。例えば,パソコンに32Mバイトのメモリーが装備されているなら,0番地~32M番地のアドレスを振る。このアドレスを指定して,任意のメモリー領域に対して1バイトのデータを入出力できることになる。

図2●アドレスを指定して入出力の対象を指定する
CPUが,どのアドレスを利用するかを指定する
 この仕組みは,I/Oとのデータ入出力でも同様である。CPUが持つピンの中には,メモリーとI/Oのどちらに対して読み書きを行うかを示す信号を出すものがあるので,同じ値のアドレスがメモリーとI/Oに割り付けられていても問題ない(図2[拡大表示])。

ハードを抽象化したソフトから利用する

 さて,ここでハードウエアとソフトウエアの関係を振り返って置こう。例えばメモリー上のある番地に,値を覚えさせるような場合を考える。

 これまでの通り,ハードウエア的にはアドレスを指定する。しかし,プログラム・コードで直接アドレスを指定することはほとんどない。メモリーとの入出力には変数を利用するし,I/Oとの入出力には専用のプログラム命令を利用するからだ。アドレスを指定した物理的な入出力は,プログラム言語によって抽象化してあるのである。どのような命令を利用するかは,プログラム言語の種類によって異なる。

図3●変数を使ったメモリー入出力プログラム
 例を見てみよう(図3[拡大表示])。変数Aに123というデータを格納し,変数Aの値を変数Bに代入するBASICのプログラムである。

 プログラマにここで意識してほしいのは2つある。1つは,変数はメモリーを表すものであること,もう1つは,メモリーには物理的な位置を表すアドレスと格納されたデータという2つの属性があるということ,である。

 図3の変数Aと変数Bは,どちらも何らかのアドレスを指しているはずである。しかし,プログラマはそれを意識する必要がない。変数名が異なれば,それらが重複しないアドレスを指すように,プログラム言語が適切なアドレスを変数に割り当ててくれるからだ。仮に変数Aが100番地を表しているとしよう。変数Aのデータ型は2バイトを格納できるInteger型である。したがって,変数Bのアドレスは,100番地の2バイト先の102番地となるはずである。

入出力はデバイス・ドライバ経由で

図4●ファイル出力はプログラム命令を利用する
 もう1つ例を見てみよう(図4[拡大表示])。MyFile.datという名前のファイルに「こんにちは」という文字列データを書き込むBASICのプログラムである。ファイルに対する書き込みは,ディスク装置用のI/Oを指定するアドレスへの出力命令となる。

 だが,変数でディスク装置を表すことはできない。Open,Print,およびCloseという3つのプログラム命令のセットではじめてディスク装置への書き込みが可能となる。これは,I/Oとの入出力はプログラマが作成したプログラムによって直接実行するのではなく,「デバイス・ドライバ」と呼ばれるI/O入出力専用のプログラムを仲介させて間接的に実行するからである。

 SCSIボードなどの拡張ボードを購入すると,フロッピ・ディスクが同こんされていることがある。このフロッピ・ディスクの中には,拡張ボードに装備されたI/Oと直接入出力を行うためのデバイス・ドライバが収録されている。これは普通,その拡張ボードを開発したメーカーが作成したものである。このドライバ・ソフトとOSの機能のおかげで,我々プログラマは直接ハードウエアを制御する必要がない。意識してほしいのは,プログラム・コードにおいてI/Oとの入出力がデバイス・ドライバとの入出力に抽象化されているということだ。

 デバイス・ドライバに関しては,本セミナーの第3回で詳しく説明する予定である。

2進数と16進数

 さて,CPUがデータをどのようにやりとりするかは,おおまかな仕組みをつかめたのではないだろうか。次は,ハードウエアの観点から,コンピュータが取り扱うデータについて見てみよう。コンピュータのデータと言えば2進数である。では,なぜ2進数を利用するのだろうか。

 まず2進数についてちょっと確認しておこう。10進数が0,1,2,3,4,・・・,9,10,と9まで数えてけた上がりするのに対し,2進数では0,1,10,と1まで数えてすぐにけた上がりする。10が10進数なら「じゅう」と読むが,2進数では10進数との混乱を避けるために「いち・ぜろ」と読む。これは,2けた以上の2進数の場合でも同様で,例えば00110101なら「ぜろ・ぜろ・いち・いち・ぜろ・いち・ぜろ・いち」と読む。

 2進数はコンピュータのために発明されたものではないが,多分にコンピュータ的なところがある。データのサイズに合わせて,上位けたに0を置くからである。2進数の1を単に1と表すことは滅多にない。データ・サイズが8ビットなら「00000001」のように8けたで,16ビットなら「0000000000000001」のように16けたで表すのである。

 さて,ここからが本題である。コンピュータを構成するICはすべて,ピンの状態は電圧0Vまたは+5Vのいずれかの状態になっている*8。正確には+5Vと0Vに加えて,回路から電気的に切り離された状態を示す「ハイ・インピーダンス状態」という状態もあるが,ここでは気にしなくて良いだろう。

 0Vと+5Vの状態が数値の0と1を表しているものとみなす。ICのピンの並びを数値のけたに対応づける。この0と1,つまり0Vと+5Vがピンに並んでいる状態を,複数のけたと見なすことが,ハードウエア的にも理にかなっているような気がしないだろうか。

図5●人間の指とICの指人間の指は10本あったから10進数が自然だったのだろう。コンピュータの指は2本しかないので,2進数が自然
 実際その通りで,コンピュータの内部では2進数が使われるようになったのである。人間は,たまたま手の指が全部で10本あったので10進数を使っているが,コンピュータの手であるICのピン1本は,0Vと+5Vの2本指だったのである。このように考えて納得してほしい(図5[拡大表示])。

 2進数の1けた,すなわちICのピン1本で表わせる情報量を「ビット」(bit)と呼ぶことはご存知だろう。ビットという言葉の本来の意味は「破片,かけら」である。データを構成する1ビット,1ビットが,情報の破片というイメージを持つことはできないだろうか。2進数のけたのこともビットと呼ぶ。“かけら”を並べた状態を想像してほしい。「8けたの2進数」は,「8ビットの2進数」と呼べる。

 これは,ICのピン1本が2進数の1けたを表していることを考えれば当然のことだ。2進数で数を表現するとき,データのサイズに合わせて上位けたに0を置くことを思い出してほしい。例えば,8本のピンがあるのにデータを単に1と表したのでは,最下位けたのピンの状態が1になっていることがわかっても,その上位7けたのピンの状態が不明確である。00000001と表すことで,すべてのピンの状態が明確になる。

16進を使う訳

表1●10進数,2進数(8ビット),および16進数の対応
 とはいえ2進数は,人間にとって分かり難いものである。けた数が多くなってしまうので,取り扱いも面倒になる。

 そこで,ハードウエア的な生々しいデータを表すために,2進数の代わりに16進数を使う方法が一般的になった。ちょうど2進数の4けたが16進数の1けたに相当するので,けた数を1/4に減らせるとともに,変換も簡単である。

 ここでは16進数についてちょっと確認しておこう。過去に学んだが忘れてしまった人もここを読んで思い出してほしい。

 16進数では,0,1,2,3,4,...,9,A,B,C,D,E,F,10とFまで数えてけた上がりする。16進数を表すには,16種類の記号が必要となり,9以上の値を表す記号としてA~Fが使われる。16進数で表現された数値も2進数と同様に各けたの数字をそのまま読む。3Aが16進数なら「さん・えい」と読むのである。2進数と同様に16進数でも,データ・サイズに合わせて上位けたに0を置く。

 あらゆる10進数,2進数,および16進数は,相互に変換することができる。当たり前のことではあるが,10進数,2進数,および16進数の対応は1対1である(表1[拡大表示])。

 さて,2進数を16進数で表現すると,けた数が1/4に減らせて変換も簡単という話に戻る。実際にやってみよう。00111010という2進数は,16進数で3Aとなる。4けたずつ,0011と1010に分けて,2進数0011が16進数3,2進数1010が16進数でAである。つなげれば3Aだ。確かにけた数は1/4である。

表2●すべての桁が1である2進数と16進数および10進数の対応
 表1をすべて暗記する必要はないが,覚えておいてほしいことがある(表2[拡大表示])。4ビット~32ビットのぞれぞれで,すべてのけたが1である2進数が10進数や16進数でいくつになるか,である。あなたがプログラムをコーディングするときに,この後で説明する「補数」と合わせて,どの変数型を選択するかを判断するための,きわめて基本的な知識となる。

足し算しかできないコンピュータ

 次はデータの計算を解説しよう。まず最初に,知っておいてほしいことがある。ソフトウエア的には,加減乗除に加えて三角関数など自在な計算ができるコンピュータだが,ハードウエア的には加算しかできないのである。足し算だけだ。疑われてもしょうがないが,これは物理的に事実である。

 加算しかできないので,減算は実現できない,と言っているのではない。加算だけで減算を実現する技があるのだ。10進数で5 - 3という減算を行う場合を例にして,減算を加算で実現する仕組みを説明しよう。

 5 - 3の演算結果は2である。5 - 3は,5 + (-3)と表すことができる。ここまでは簡単だろう。ここで,(-3)の部分を,正の値で表すという“裏技”がある。裏技の名前は「補数」という。これを利用する。

 補数は,補数と,元の数の絶対値を足すとけたが1つあがる数を示す。-3の補数は7である。7と3を足すと,ちょうどけたがあがって10になる。「減算は,補数の加算に変換してけた上がりを無視する」というルールを利用すれば,(1)引く数の補数を求めて,(2)それを足し算して,(3)けた上がりを無視する――というステップで,足し算だけで引き算を実現できるはずだ。よく分からなくても,実際にやって見れば分かるはずである。やってみよう。

5 - 3= 5 +(-3)
= 5 +(7 - 10)… (1)補数は7
=(5 + 7)- 10… (2)補数を足し算
= 12 - 10… (3)けた上がりを無視
= 2

 最後に10を引いている部分は,けた上がりを無視するという処理で,減算ではない。

 補数を使えば加算で減算を実現できることには,感激していただけたのでないだろうか。

 減算さえできてしまえば,こっちのものである。乗算は加算の繰り返しでいい。4 × 3という乗算なら4を3回加算すればよいのである。

 除算も減算の繰り返しで実現できる。10 ÷ 3という除算なら10から3が何回引けるかをカウントしておく。カウントの結果が除算の商である。もしも,余りを残さず小数点以下の値も求めたいなら,余りをけた上げして,再度引き算を繰り返せばよい。結局のところ,加算だけで四則演算のすべてが可能となる。