前回は条件フラグ共を「見て飛ぶ」条件ジャンプ命令Jccでした。今回はカウンタ・レジスタCXを「見て飛ぶ」(ついでの操作もあるけど)条件ジャンプ命令どもです。16ビットのx86はこのCXの扱いといい、ぜんぜん汎用レジスタでないです。御先祖から引きづっているものが多過ぎる?でもま、ループなど作る時には便利な命令どもです。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
カウンタCXを「見て」飛ぶJumpども
例によって、1個のオペコードに二種類のニーモニックが割り当てられている命令がありますが、ゼロといってもよいし、イコールと言っても良いと。哲学的?
上記の4命令とも全て「汎用」レジスタCXの値を見て飛びます。シンプルなJCXZは「CXが0だったら飛ぶ」だけですが、LOOPの名を持つ命令どもは、まずCXをデクリメントして、その結果が0でなければ「飛ぶ」かもしれんです。「かも」とうのは、LOOPEとLOOPNEの場合、ゼロフラグもあわせて確認するからです。それら条件を表にしたものが以下です。
mnemonic | CX | ZF |
---|---|---|
JCXZ | =0 | – |
LOOP | DEC CX; ≠0 | – |
LOOPZ/LOOPE | DEC CX; ≠0 | 1 |
LOOPNZ/LOOPNE | DEC CX; ≠0 | 0 |
ありがちなFORループのようなものを作るには向いている(かもしれない)命令どもです。RISC派閥からは余計なお世話だ、シバリが多過ぎる、と文句を言われそうな気もしないでもないです。
なお上記の命令どもは全て8ビットのディスプレースメントをとり、「飛ぶ」場合には、符号拡張された16ビット値としてIPに加えられます。
今回練習のアセンブリ言語ソース
強力なx86用アセンブラNASM用のソースです。JCXZについては飛ぶのと飛ばないの、両方通してますが、LOOPは2回「回る」ときだけ、LOOPEはノンゼロなので飛ばないときだけです。手抜きだよ。
segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop test: mov cx, 0 jcxz lbl_cx0 mov ax, 999 lbl_cx0: mov ax, 0 mov cx, 2 jcxz fin loop_target: inc ax loop loop_target mov cx, 1000 loop_target2: cmp ax, 0 loope loop_target2 fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 resb 1024 * 63 segment stack class=STACK resb 2048 stacktop:
アセンブルして実行
MS-DOS互換で機能強化されてるフリーなFreeDOS上、以下のステップで上記アセンブラソース jcxz.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)
nasm -f obj jcxz.asm wlink name jcxz.exe format dos file jcxz.obj debug jcxz.exe
赤枠で囲ったのが今回ターゲットの命令どもです。インテル式ではLOOPあるいはLOOPZと表記する筈が、後ろにWついているのはAT&Tが混じっている?よくわからんのう。。。
まずは1個めのJCXZから。CXレジスタが0であれば オフセット0x0015番地へ飛ぶと。ただしコード上はJCXZ命令の次の命令の先頭番地を基点とする相対番地なので、0x0010+2+3で0x0015っす。JCXZは0だったら何もしないでループを抜ける、ってなときにお役立ちとインテルのデータシートには書いてあるけどホントか?
さらにCX=0x0002で2回ループする LOOP命令です。1回目に関係するところが赤色、2回目がオレンジ色の枠つけてあります。
CXがカウントダウンされていき、0になったら飛ばずに下に落ちるっと。
最後はLOOPZ(LOOPE)です。CXが0でなく、かつZFが立っていたら飛ぶ命令です。以下の赤枠ではCXにはデカイ値が入ってますが、NZになっているので飛ぶ条件を満たしませぬ。
下に落っこちて完了と。まあ、思った通りの動作だったな。