4.条件分岐をやってみる
作成日:2005年04月07日



あらゆるプログラムは順接繰り返し分岐で記述できると言われています.順接はプログラムを上から順に実行すること,繰り返しは名前のとおり,Cで言うfor文とかwhile文ですかね.分岐はif-elseやswitch-case文ですね.で,今回はこの分岐をアセンブラでやってみます.

元となるプログラムは次のようなものです.


#include <stdio.h>
int main (void) {
    int a = 10;
    if (a > 0) printf ("%d is plus number.\n", a);
    else if (a < 0) printf ("%d is minus number.\n", a);
    else printf ("%d is zero number.\n", a);
    return 0;
}

実行結果は当然次のようになります.


10 is plus number.


ポイントはif-else文で表される条件分岐をアセンブラでどう実現するかです.これには,ジャンプ命令を用いることで実現できます.

JMP命令は,第1オペランドで指定されたアドレスにプログラムの制御を移すという命令です.これを使えば条件分岐が実現できる・・・・気がしますが,実はJMP命令は無条件にジャンプを実行する命令です.よって,上記のif文のように「aの値が正だったらジャンプ」ということは出来ません.

では,どうしたらいいのか?無条件ジャンプ命令があるなら,当然条件付ジャンプ命令があります.というわけで,条件付ジャンプ命令Jccを用いて条件分岐を実現します.

さて,Jcc命令なんですが,Jcc命令は条件ごとに命令が存在します.例えば,値が「より大きい」場合にジャンプするJG,「等しい」場合にジャンプするJE,「より小さい」場合にジャンプするJL,「ゼロ」の場合にジャンプするJZなどなど・・・・・さらに,等しくない場合,より大きいか等しい場合,より小さいか等しいなどなど,命令の数は結構な量になります.

これら「より大きい」「より小さい」等の判定は,前回使ったCMP命令を使います.つまり,CMP命令で修正されたステータスフラグを元にJcc命令の条件を判定します.前述の「より大きい」条件ならば,「ZF=0かつSF=OF」となります.Jcc命令の詳細は下に記載しました.ステータスフラグの詳細は前回の内容を参考にしてください.


では,Cで書いた条件分岐プログラムをアセンブラで実現してみましょう.先ほどmain関数内に書いたif分の内容をそのままアセンブラに置き換えたif_asm関数を用いたソースファイルは次のようになります.

#include <stdio.h>

void if_asm (int a) {
	int b;
	__asm {
		mov eax, a
		cmp eax, 0
		jg CASE0	// >
		jl CASE1	// <
		jz CASE2	// =
CASE0:
		mov b, 0
		jmp FINISH
CASE1:
		mov b, 1
		jmp FINISH
CASE2:
		mov b, 2
		jmp FINISH
FINISH:
	}
	switch (b) {
	case 0: printf ("%d is plus number.\n", a); break;
	case 1: printf ("%d is minus number.\n", a); break;
	case 2: printf ("%d is zero number.\n", a); break;
	}
}

int main (void) {
	int a = 10;
	if_asm (a);
	return 0;
}

実行結果は次のようになります.


10 is plus number.



プログラムは見てわかるとおりです.eaxレジスタに入れられたaの値と0をCMP命令で比較します.その結果,ステータスフラグの内容が変わり,それに応じてJG,JL,JZのいずれかでジャンプします.ジャンプ後のそれぞれの位置で,内容に応じてbに対応した値を代入し,終了します.その後,bの値に応じて文字列を出力します.



今回新たに使用した命令は次のとおりです.

JMP - ジャンプ
リターン情報を記録しないで,プログラムの制御を命令ストリーム内の別の点に移す.第1オペランドには,ジャンプ先の命令のアドレスを指定する.このオペランドには,即値,汎用レジスタ,またはメモリロケーションを使用できる.
相対オフセット(rel8,rel16,またはrel32)は,アセンブリコードでは一般的にラベルとして指定されるが,機械語レベルでは符号付きの8,16,または32ビットの即値としてコード化され,命令ポインタに加算される.
命令説明
JMP rel8次の命令との相対分量分だけ相対shortジャンプ
JMP rel16次の命令との相対分量分だけ相対nearジャンプ
JMP rel32次の命令との相対分量分だけ相対nearジャンプ
JMP r/m8r/m16 で指定されるアドレスに絶対間接shortジャンプ
JMP r/m16r/m16 で指定されるアドレスに絶対間接nearジャンプ
JMP r/m32r/m32 で指定されるアドレスに絶対間接nearジャンプ

Jcc - 条件付きジャンプ命令
EFLAGSレジスタ内のステータスフラグ(CF,OF,PF,SF,およびZF)の1つ以上の状態を調べ,それらのフラグが指定された状態(条件)にある場合は,第1オペランドによって指定されたターゲット命令へのジャンプを実行する.各命令に特定の条件コード(cc)が対応しており,テストされる条件を示している.条件が満たされなかった場合は,ジャンプは実行されず,Jcc命令の次の命令から実行が継続される.
命令説明
JA rel8より上(CF=0およびZF=0)の場合shortジャンプ
JAE rel8より上か等しい(CF=0)場合shortジャンプ
JB rel8より下(CF=1)の場合shortジャンプ
JBE rel8より下か等しい(CF=1またはZF=1)場合shortジャンプ
JC rel8キャリーがある(CF=1)場合shortジャンプ
JCXZ rel8CX レジスタが0の場合shortジャンプ
JECXZ rel8ECX レジスタが0の場合shortジャンプ
JE rel8等しい(ZF=1)場合shortジャンプ
JG rel8より大きい(ZF=0およびSF=OF)場合shortジャンプ
JGE rel8より大きいか等しい(SF=OF)場合shortジャンプ
JL rel8より小さい(SF<>OF)場合shortジャンプ
JLE rel8より小さいか等しい(ZF=1またはSF<>OF)場合shortジャンプ
JNA rel8より上でない(CF=1またはZF=1)場合shortジャンプ
JNAE rel8より上でなく等しくない(CF=1)場合shortジャンプ
JNB rel8より下でない(CF=0)場合shortジャンプ
JNBE rel8より下でなく等しくない(CF=0およびZF=0)場合shortジャンプ
JNC rel8キャリーがない(CF=0)場合shortジャンプ
JNE rel8等しくない(ZF=0)場合shortジャンプ
JNG rel8より大きくない(ZF=1またはSF<>OF)場合shortジャンプ
JNGE rel8より大きくなく等しくない(SF<>OF)場合shortジャンプ
JNL rel8より小さくない(SF=OF)場合shortジャンプ
JNLE rel8より小さくなく等しくない(ZF=0およびSF=OF)場合shortジャンプ
JNO rel8オーバフローがない(OF=0)場合shortジャンプ
JNP rel8パリティがない(PF=0)場合shortジャンプ
JNS rel8符号がない(SF=0)場合shortジャンプ
JNZ rel8ゼロでない(ZF=0)場合shortジャンプ
JO rel8オーバフローがある(OF=1)場合shortジャンプ
JP rel8パリティがある(PF=1)場合shortジャンプ
JPE rel8パリティが偶数(PF=1)の場合shortジャンプ
JPO rel8パリティが奇数(PF=0)の場合shortジャンプ
JS rel8符号がある(SF=1)場合shortジャンプ
JZ rel8ゼロ(ZF=1)の場合shortジャンプ


なお,前回の
LOOP命令はshortジャンプでしたが,今回のJMP命令はnearジャンプが含まれています(Jcc命令は量が多すぎるためnearジャンプを割愛しています).興味のある人は,命令セットリファレンスを調べてみてください.

さて,今回作成したプログラムではアセンブラ内で確かに条件分岐をしていますが,その結果を出力するときに再び条件分岐しています.これでは効率が悪いですね.この問題は,インラインアセンブラ内で関数を呼び出すことができれば解決します.というわけで,次回.



もどる

Gポイントポイ活 Amazon Yahoo 楽天

無料ホームページ 楽天モバイル[UNLIMITが今なら1円] 海外格安航空券 海外旅行保険が無料!