TSG 部報 第 199 号・新入生歓迎号


初心者のための 8086 講座<第2回>

高野

どうも高野です。
95でこの原稿のデータをぶっ飛ばされてしまいました。(T_T)
やはり8MBで95なんぞを使ったのが悪かったらしい。
貧乏人は損じゃのう....

どのようにデータが格納されているのか?

 前回パソコンはメモリにデータを格納するとき2進法で記憶させていると書きましたが、そのことについて、もう少し詳しく説明してみたいと思います。2進法とは0と1によってすべての数を表現することです。パソコンではこの1と0を電気が有るか無いかで判断しているのです。たとえば■を1、□を0とした場合、

  データ    アドレス

□□■□■■□□ 0x01
□■■■□□□■ 0x02
■■□■□■■□ 0x03
□□■□■□□□ 0x04
■□□■■■□□ 0x05
□□□■□□□■ 0x06
 上の表を参考にしてみると、アドレスが0x04のデータは00101000です。このように8ビットを一つの単位としてとらえ、これを1バイトと呼ぶわけです。

アセンブラ言語

 前回マシン語は16進法で表すことが一般だと書きましたが、よっぽどコンピュータに精通している人でない限り内容を理解することは不可能です。(いにしえのプログラマでZ80のマシン語をすべて16進法で暗記していた人が存在したらしい....)そこで、人間がマシン語を扱う場合には、マシン語と一対一に対応したアセンブリ言語というモノを使います。たとえばマシン語でB01Aとはアセンブリ言語で示すと「MOV AL,1A」で表されます。マシン語には1つ1つ記号が割り当てられており、その記号のことをニーモニックと呼びます。(たとえば今さっき例に挙げたMOVなど)ニーモニックからマシン語に変換する作業のことをアセンブルといいます。この逆の作業を逆アセンブルまたはディスアセンブルといいます。

データ転送命令

 データ転送命令とは8ビットまたは16ビット単位でCPUを通してデータのやりとりをする命令のことです。データ転送命令でもっともよく使われるのはMOVです。

MOV   受け取る側       送り出す側
    (レジスタ、メモリ)  (レジスタ、メモリ、数値)

 MOVは右側で指定したデータを左側のデータ格納場所に移動させる、すなわち「転送」する命令です。データの移動する向きは「右から左」です。送り出す側はデータが転送された後にも、はじめのデータが残ります。

メモリからレジスタにデータを転送するときのことを「ロード」
レジスタからメモリにデータを転送することを「ストア」

と、言います。

例をあげて説明していきましょう。

16Hをいう数値をALレジスタに転送したいとき

MOV AL,16H

オフセットアドレス(オフセットアドレスの解説は前号を見て下さい)「0541H」にALレジスタの内容をストアしたいとき

MOV [0541H],AL

 ここで注意してもらいたいのは受け取る側に[]がついているところです。これをつけることで数値とアドレスを分別しているのです。だからこの場合「MOV 0541H,AL」は意味をなさなくなり、また「MOV AL,16H」と「MOV AL,[16H]」とでは全く意味が違うと言うことです。後者はオフセットアドレスが0016HのメモリのデータをALに転送するという意味です。
 次は1ワード、つまり一度に16ビット取り扱う場合です。1ワードの1A56HというデータをAXレジスタに転送するには

MOV AX 1A56H

となります。1バイトの時と同じです。1AHはAHに入り、56HはALに入ります。
 逆にレジスタからメモリにストアする場合も1バイトの時と変わらないのですが、データの格納のされ方に注意する必要があります。というのも、8086系CPUの特徴として「メモリ上のデータは実際のデータの上位バイトと下位バイトとが入れ替わる」のです。詳しく説明すると

MOV [0123H],AX

 という命令をCPUに与えたとします。このときAXのデータは16ビットですから、オフセットアドレスが0123Hと0124Hのメモリに格納される訳です。このときAH(上位バイト)は0124HにAL(下位バイト)は0123Hに格納されます。

AXレジスタからBXレジスタの値をオフセットアドレスとしてメモリへストアしたいとき

MOV [BX],AX

 ここでは[]を使うことで、BXに直接AXから転送するのではなく、BXレジスタの指し示した(ポインティングした)メモリへAXの内容を転送します。この考え方は実はC言語でいうところのポインタそのものなのです。(ポインタについては部長の記事を参照して下さい)これの応用として、

MOV [BX + 5],AX

とすることで、BXから5番目のメモリにAXのデータを転送することもできます。

 さて以上で大体MOV命令の特徴がわかっていただけたでしょうか?MOV命令はマシン語のプログラムを組む上でもっともよく使う命令なので、しっかり理解して下さい。

 ところでデータの格納場所はご存じの通りレジスタとメモリの2種類があります。レジスタにも前号で書いたように特殊なモノもあり、なかでもセグメントレジスタは操作が制限されています。例えば、CS(コードセグメント)はMOV命令を使って直接書き込むことは できません。よってセグメントレジスタは他のレジスタと区別して扱います。
 データ転送は「セグメントレジスタ、その他のレジスタ、メモリ」という3つのデータ格納場所とデータそのものを様々に組み合わせて行うことができます。たとえば、

MOV AL,0AAH
MOV [0123H],AL

は一つの命令に書き直すことが可能で、

MOV [0123H],0AAH

と数値を直接メモリに書き込むことができます。
しかし、

MOV AL,[0123H]
MOV [3210H],AL

MOV [3210H],[0123H]

と書き直すことはできません。
 このようにデータを転送できる組み合わせが決まっています。この組み合わせのことをアドレッシングモードといいます。

レジスタ(AX,BX,CX,DX,SI,DI,BP,SP)
データ(10H,1234H等)
セグメントレジスタ(CS,DS,ES,SS)
メモリ([01234h][45ACH]等)
とするとき

レジスタ<---->レジスタ
データ----->レジスタ
データ----->メモリ
セグメントレジスタ<---->レジスタ
 (ただしCSレジスタに値を転送することはできません)
レジスタ<---->メモリ
セグメントレジスタ<---->メモリ
 (ただしCSレジスタに値を転送することはできません)

以上の様な組み合わせが可能です。

 レジスタを使ったアドレス指定を先に少しだけ説明しましたがここでもう少し詳しく説明します。たとえばMOV AX,[BX+5]とした以外にもMOV AX,[BX+SI+5]と書くこともできます。ここでの「5」の値のことをディスプレーメントといいます。ここでもアドレッシングモードの時と同じく、レジスタとディスプレーメントの組み合わせに制限があります。

レジスタとディスプレーメントの組み合わせ
(BX,BP) + ディスプレーメント 例:[BP+1]
(SI,DI) + ディスプレーメント 例:[SI+6]
(BX,BP) + (SI,DI) + ディスプレーメント 例:[BX+DI+4],[BP+SI+6],[BX+DI+7]

ちなみにディスプレーメントで指定できる値は1バイトまたは1ワードです。

算術演算命令

 前号で説明したようにCPUにはデータの各種演算をやる機能が用意されています。この各種演算はレジスタやメモリの中で行われるのでは無くて、実際にはALU(Arithmetic and Logic Unit)と呼ばれている演算部で行われます。演算の対象となるレジスタやメモリからデータをこのALUに転送して、結果をメモリやレジスタに転送するのです。その時演算の結果だけではなく、その結果が0であったか負の数であったか等の状態をCPU内のフラグレジスタにセットします。ここでは8086のもつ算術演算命令の説明をします。算術演算命令もデータ転送命令と同様にアドレッシングモードが決まっています。

加算減算命令

はじめに足し算をしてみましょう。足し算の命令は

ADD   足したい数   足したい数

です。計算結果は左側に入りますので、左側には必ずレジスタかメモリを指定してやらなくてはなりません。(以後レジスタと言ったらセグメントレジスタ以外のモノを指すことにします。)例えばALレジスタに1バイトのデータ2AHを加算する場合

ADD AL,2AH

と書きます。ALに最初11Hが入っていた場合、この命令の後にはALには3Bが入っていることになります。
次に引き算ですが、これも簡単で

SUB   引かれる数   引く数

です。ALレジスタからオフセットアドレスが1234Hに格納されているデータで減算するとき、

SUB AL,[1234H]

です。この場合も計算結果がALレジスタに格納されることは言うまでもありません。

 次はインクリメントとデクリメントです。この二つの言葉はあまり聞かれない言葉ですが、インクリメントとはレジスタを-1、デクリメントとはレジスタを-1することを言います。ここでなぜこんな命令が必要なのか疑問に思う方がいるかもしれません。単純にADD命令を使えばいいような気もします。しかし、マシン語のプログラミングでは+1や-1を頻繁に行います。そこで、ADD命令よりも高速な(つまりクロック数が少ない)この命令を使います。これによって実行効率を高めるわけです。インクリメントの命令は

INC   数を一つ増やしたいレジスタ

とします。

INC AL

ADD AL,1

と同じ意味です。同様にしてデクリメントの命令は

DEC   数を一つ増やしたいレジスタ

と書きます。

それでは復習として少しマシン語で書いたプログラムを書いてみましょう

MOV BL,34H  ;BLレジスタに1バイトデータ34をロード
MOV AL,BL  ;ALレジスタにBLレジスタに転送
INC AL  ;ALをインクリメント
MOV [A123H],AL  ;ALレジスタのデータをストア
DEC BL  ;BLをデクリメント
ADD AL,BL  ;ALとBLを足す
SUB AL,BL  ;ALからBLを引く

なんかあまり意味のないプログラムですね(^_^;

 2つのデータを比較する命令をコンペア命令、比較命令といいます。具体的に何をやるかというと、引き算をやるのですが、引き算の結果そのものをメモリやレジスタ上には残しません。じゃあいったいなんのための命令かわかりません。そこでこの算術演算命令の項のはじめで述べたことを思い出して下さい。ALUで計算された時に一緒にその演算結果の状態をフラグレジスタに保存されると言うことを書きました。そうです、この比較命令はこのフラグレジスタに状態を保存する命令なのです。

 ここで詳しくフラグレジスタの説明をしておきましょう。フラグレジスタとは読んで字の如く「旗」レジスタなのですが、このレジスタにはビットごとに意味を持っていて、個々のビットごとつまりフラグごとに単独に動作します。そのため他のレジスタのように16ビットのレジスタとしてプログラムから自由に操作することができません。フラグを1にすることを「セット」するといい、0にすることを「リセット」または「クリア」と言います。フラグレジスタのビットごと(フラグごと)の持つ意味を下の表に書きます。

フラグレジスタ■■■■OFDFIFTFSFZF■AF■PF■CF
OFオーバーフローフラグ 符号付き演算で桁あふれが生じたときにセットさせる
DFディレクションフラグ ストリング操作命令においてポインタの増減方向を示す
IFインタラプトイネーブルフラグ セットすると外部割込を受け付けなくなる
TFトラップフラグ シングルステップモードで実行するときのフラグ
SFサインフラグ 演算結果の符号を表す。負ならばセットされる
ZFゼロフラグ 演算結果がゼロであればセットされる
AF補助キャリーフラグ BCD演算で使用されるキャリーフラグ
PFパリティーフラグ 演算の結果、1となるビット数が偶数の時セットされ、奇数の時クリアされる
CFキャリーフラグ 演算の結果、桁上がりが生じた場合にセットされる

全角一文字が1ビットです。■は何も役割を割り当てられていないことを示しています。

「ストリング命令」とか「シングルステップモード」とか聞き慣れない言葉が出てきますがここではあまり気にしないで下さい。

フラグレジスタはこのように命令ごとに影響を受けるフラグが決まっています。もちろんフラグに何も影響を与えない命令も存在します。

 さて話を比較命令に移して考えて見ましょう。比較命令は

CMP   比較されるデータ   比較するデータ

 比較されるデータから比較するデータを減算しその結果は保存されませんが、フラグレジスタには保存されます。比較命令の場合はSF,ZF,PFが変化します。この比較命令は単独では使用されず、「条件分岐命令」と組み合わせて使用されます。具体的な使用法は「条件分岐命令」のところで解説します。

乗除算命令

 乗除算命令は他の演算とは異なり使用できるレジスタが決まっています。使用できるレジスタはAXレジスタまたはALレジスタです。DXレジスタも使われることもあります。
かけ算の命令は

MUL   掛ける数

 掛ける数が8ビットの時はALとかけ算をし、その結果をAXに転送されます。掛ける数が16ビットの時はAXとかけ算をし、結果を上位ビットをDXに、下位ビットをAXに転送します。たとえば

MUL BX

とかけばAXとかけ算をし、その結果をDXとAXに転送するわけです。

わり算の命令は

DIV   割る数

 割る数が8ビットの場合はAXレジスタをわり算し、商をALレジスタに、余りをAHレジスタに転送します。割る数が16ビットの時はDXを上位16ビットにしAXを下位16ビットとして、割られる数を32ビットにしわり算をします。その後、商をAXレジスタに、余りをDXレジスタに転送します。MUL,DIVともに割る数にはレジスタ、数値、メモリのオフセットアドレスを指定してやることができます。

PTR演算子

 さて上で見たように乗除算の命令では指定するデータの大きさによって、命令の意味が異なってくることがわかりました。直接値やレジスタの場合は8ビットか16ビットかはっきりしているので問題はないのですが、メモリを対象としオフセットアドレスを指定した場合1バイトなのか1ワードなのかはっきりしません。そのことは明確にするためにアセンブラ言語では「PTR演算子」なるモノを利用します。

BYTE PTR [オフセットアドレス]
WORD PTR [オフセットアドレス]

前者はバイト単位のデータをメモリから読み出し、後者はワード単位(つまり2バイト)でデータを読み出します。例えば

INC [0123H]

ではだめで

INC BYTE PTR [0123H]

または

INC WORD PTR [0123H]

としなければなりません。

またデータ転送命令等の場合も同様です。

MOV [0FFFH], 3AH

これは一見あっているようですが、実はエラーが出るのです。

MOV BYTE PTR [0125H],3AH

または

MOV WORD PTR [0125H],3AH

としなければなりません。前者は納得行きますが、後者はなにかおかしな気もします。しかし、[0126H]には「00H」がはいるのです。


[目次へ]

g541119@komaba.ecc.u-tokyo.ac.jp