これまで,何回か主記憶(メモリー)の構造を図をつかって説明してきました.
主記憶は,1バイトのメモリーが1列に連続的に並んだもので,1バイト単位にアドレスがついていると説明してきました.また,int型の整数やfloat型の実数は4バイト,double型の変数は8バイトで表現されると説明してきました.(整数は4バイトでない場合もありますが).
CPUがデータを読み書きするときは,バイトを単位に読み書きします.しかし,この1バイトのメモリには,更に微細な構造があります.すなわち,1バイトのメモリーはより細かく見ると8個のセルで構成されています.図で表現すると次のようになります.
|
|||||||||
ビット | 7 6 5 4 3 2 1 0 |
この1個のセルのことをビットとよびます.ビットは記憶の最小単位で,1または0(つまり2進数)を記憶します.これら,8個のビットは切り離すことはできないと考えて下さい.CPUはメモリーのデータを8ビットまるごと(言い換えれば,1バイトを単位として)読み書きします.また,主記憶のアドレスというのは,この8ビットで構成される1バイトづつに割り当てられた番号ということになります.
ついでに,左端のビットのことを最上位ビット,右端のビットを最下位ビットとよぶことも覚えておいて下さい.また,最下位ビットを第0ビット,最上位ビットを第7ビットと番号をつけて呼ぶこともあります.(最上位ビットを8,最下位ビットを1と番号ずけする人もいます)
1ビットは,0または1を記憶するとのべましたが,いま,8ビットすなわち1バイトで数値を表現することを考えてみましょう.
次に示すように数値を正の整数に限れば0〜255までの256通り(2の8乗とおり)の数値が表現できます.データを下の図のように1と0で表現したものを「ビットパターン」と呼ぶことも覚えておいて下さい,
10進:0 |
|
||||||||
10進:1 |
|
||||||||
10進:2 |
|
||||||||
10進:3 |
|
||||||||
10進:4 |
|
||||||||
10進:5 |
|
||||||||
10進:6 |
|
||||||||
・・・ | |||||||||
10進:255 |
|
次に,10進数の1,2,4,8,・・・,128の場合についてビットパターンを表示してみると,次の図のように表すことができます.
10進:0 |
|
||||||||
10進:1 |
|
||||||||
10進:2 |
|
||||||||
10進:4 |
|
||||||||
10進:8 |
|
||||||||
10進:16 |
|
||||||||
10進:32 |
|
||||||||
10進:64 |
|
||||||||
10進:128 |
|
これらの数値の間には次のような関係があります.2は1の2倍,4は2の2倍,8は2の2倍,16は8の2倍,32は16の2倍,64は32の2倍,128は64の2倍となります.
これらのビットパターンを見比べて下さい.そうです.2倍される度に1が左へ1つづつビットシフトすることが分かると思います.
逆に,例えば,128から始めて,次々に右へビットをシフトさせると,1/2づつ小さくなっていることが分かると思います.
そうです.
数値を2倍するには,ビットを左へシフトし,1/2するには右へシフトさせればよいのです.この演算は,CPUによりますが通常の乗算(*)除算(/)で計算するより,ン十倍も計算が速いのです.(だからといって,数値計算などで使うためにあるのではありません!もちろん使えるならどこで使っても構いませんが)
C言語では,このシフト演算を表すための,演算子が用意されています.CPUそのものにもそのような演算をする機能が備わっています.
シフト演算子は演算記号(<<)や(>>)で表現します.その使い方は直感的に分かるように>>が右シフト演算,<<が左シフト演算の記号です.
例えば,
1バイトの数値を記憶する変数aがあったとしましょう.それには1が記憶されているとします.1はビットパターンで「00000001」ですね.
b=a<<2
と書けば,aのビットを2回左へシフトさせることとなり,bには,ビットパターン「00000100」が代入されます.
また,bに128(「10000000」)を代入し
b=a>>4
と書けば,aの各ビットを4回右へシフトさせることとなり,bにはビットパターンは「00001000」が代入されます.すなわち10進数の8ですね.
上の2つの式では,aのビットパターンそのものは,もとのままです.もし,aのビットパターンそのものを変えたければ,複合演算子を使って,それぞれ次のように書けばよいのです.
a<<=2
a>>=4
これは,計測器を制御したり,入出力関連のハードウェアを直接制御するプログラムを作成するときよく使われます.(このとき使用するライブラリー関数にはoutp()関数やinp()関数などがあります.参考書などで調べてみて下さい).あっ!それから,グラフィックスのデータを処理するときなどにもよく使われます.それから,通信などでもよく使われます.初心者の人には,あまり縁がない演算かもしれません.
まず,演算そのものをビットパターンを使って説明しましょう.
演算のルール: 1&1=1, 1&0=0, 0&1=0, 0&0=0
(&演算子の作用は,代数演算の積と似ていますね)
値の代入された変数aとbとのビットごとの論理積を求めるには,このルールを,変数aと変数bの対応するビットどうしに適用すればよいのです.
c=a&b
変数 a |
|
||||||||
変数 b |
|
||||||||
変数 c=a&b |
|
演算のルール: 1|1=1, 1|0=1, 0|1=1, 0|0=0
(|演算子の作用は,代数演算の和とにてますね.ただし,1|1が1となるところだけが異なります.)
値の代入された変数aとbとのビットごとの論理和をもとめるには,このルールを変数aと変数bの対応するビットどうしに適用すればよいのです.
c=a|b
変数 a |
|
||||||||
変数 b |
|
||||||||
変数 c=a|b |
|
通常のデータでは,1つの文字や数値を表すのに1,2,4,8,・・・バイトを単位に1つのデータを表現します.
ところが,ハードウェアを直接操作するプログラムでは,ビット単位で情報を表現することがあります.
例えば,通信でおなじみのRS−232Cインターフェースでは,文字の送受信をする場合,データとしての文字は8(または7ビット)で表し,1文字づつ送受信します.
しかし,インターフェースそのものは,有限の時間でデータを送受信するので,CPUが自己のペースで,文字を送り続けることはできません.複数の文字を連続的に送信するには,1文字送る度にインターフェースの状態,すなわち,いま,インターフェースがCPUからの文字を受け取って外部へ送信できる状態にあるかどうか確かめながら,1文字づつ送らなければなりません.これをハンドシェークとよびますが,これは,RS−232Cに限らずハードウェアレベルでデータをやりとりするときの一般的な方法なのです.
このようなとき,インターフェース内部の状態(あるいは,インターフェースの現在の状態)を知らせるメッセージが必要となります.このメッセージはインターフェースのもつメモリー(このような場合,メモリーをレジスターと呼びます)に書き込まれます.CPUは,そのレジスターの内容をチェックし,外部への送信が可能である場合に文字をインターフェースへ渡します.
この場合,インターフェースは,文字を「送信できる状態にある」か「送信できない状態にある」か2種類の状態を表現する必要があるわけです.2種類の状態を表現するには「1ビット」で十分です.
また,CPUがインターフェースから文字を受け取るときも,文字が受信できたかどうかレジスターをみて,文字が受信されたときデータを受け取りにいきます.この場合も文字が「受信できた」か「受信できてない」かの2通りのメッセージしか必要ないので状態は「1ビット」で表現可能です.
このようなとき,インターフェースの内部の状態をあらわすための8ビットのレジスター(メモリ)を次のように使います.
レジスタ |
|
ここで,Rxのビット:データが受信されたとき「1」,データが受信されてないとき「0」
Txのビット:データ送信可能のとき「1」,データ送信不可のとき「0」
をあらわすとします.
CPUは文字を受信するときは,Rxのビットのみをチェックすればよいわけですが,1ビットだけ読みとることはできないので,一旦,変数bに1バイト全部を読み込み,2ビット目だけ1の変数aと論理積を求めます.その結果,すなわち変数cの内容が0でなければ,Rxのビットが1,すなわちインターフェースに文字が到着ということになり,そこへCPUが文字を読みとりに行くということになるのです.このことを次の図に示します.また,もし,文字が受信できてなければRxのビットは0ですからcも0ということになります.
変数 a |
|
||||||||
変数 b |
|
||||||||
変数 c=a&b |
|
文字を送信するときは,Txのビットを同様にチェックします.ただし,この時は,当然,変数aに「00000001」を代入しておかなければなりません.
レジスターでは,2ビットまとめて3,または4種の状態を表現(あるいは,3ビットまとめて7ないし8種の状態を表現したり・・・・)する場合もあります.
□ここでは,論理演算の用途の1例のべただけですが,いろいろなプログラムを組んでいるうちに実際に遭遇することになるでしょう.
□画像処理などにもよくつかわれます.
ビット演算には,その他にXOR(演算記号^)やビットを反転(~)させる演算があります.
演算のルールのみ記しておきます.
演算のルール: 1^1=1, 1^0=1, 0^1=1, 0&0=0
(いづれかのビットが1なら1,両方のビットが0のときのみ0)
演算のルール: ~1=0, ~0=1
(~aと書けば,変数aに記憶されたすべてのビットを反転させます)