6.1 はじめに

第5章で説明したデバッグウインドウもどきのプログラムの中で、stosbという命令を使いました。 このstosb命令は、ストリング操作命令と呼ばれる命令の一つです。 ストリング操作命令には、stosbの他にmovsb, cmpsb, scasb等の命令があります。 ストリング操作命令は、その名の通り主に文字列の操作に利用される命令ですが、実は画像処理においても有用な命令です。 そこで、第6章ではストリング操作命令を使った簡単な文字列操作の例を示し、次の第7章では画像処理の例を示したいと思います。

6.2 ストリング操作命令の種類

ストリング操作命令には、大きく分けるとmovs, stos, lods, cmps, scasの5種類があります。 これらは、さらに細かく分けると全部で15種類の命令があります。 命令の末尾にbが付いていて、1バイトずつ処理をするのが、movsb, stosb, lodsb, cmpsb, scasbです。 命令の末尾にwが付いていて、2バイトずつ処理をするのが、movsw, stosw, lodsw, cmpsw, scaswです。 命令の末尾にdが付いていて、4バイトずつ処理をするのが、movsd, stosd, lodsd, cmpsd, scasdです。 これらの命令の働きをまとめたのが下の表です。

  b(1バイトずつ処理) w(2バイトずつ処理) d(4バイトずつ処理)
movs
(メモリからメモリへ代入)
[esi]の値を[edi]に代入し、
esiの値とediの値を1増減させる
[esi]の値(2バイト)を[edi]に代入し、
esiの値とediの値を2増減させる
[esi]の値(4バイト)を[edi]に代入し、
esiの値とediの値を4増減させる
stos
(レジスタからメモリへ代入)
alの値を[edi]に代入し、
ediの値を1増減させる
axの値を[edi]に代入し、
ediの値を2増減させる
eaxの値を[edi]に代入し、
ediの値を4増減させる
lods
(メモリからレジスタへ代入)
[esi]の値をalに代入し、
esiの値を1増減させる
[esi]の値をaxに代入し、
esiの値を2増減させる
[esi]の値をeaxに代入し、
esiの値を4増減させる
cmps
(メモリとメモリを比較)
[esi]の値と[edi]の値を比較し、
esiの値とediの値を1増減させる
[esi]の値と[edi]の値(2バイト)を比較し、
esiの値とediの値を2増減させる
[esi]の値と[edi]の値(4バイト)を比較し、
esiの値とediの値を4増減させる
scas
(レジスタとメモリを比較)
alの値と[edi]の値を比較し、
ediの値を1増減させる
axの値と[edi]の値を比較し、
ediの値を2増減させる
eaxの値と[edi]の値を比較し、
ediの値を4増減させる

ストリング転送命令を実行した際にesiレジスタ及びediレジスタの値を増やすか減らすかは、フラグレジスタの中のディレクション(方向)フラグの値によって決まります。 ディレクションフラグの値が0の時は増加し、1の時は減少します。 ディレクションフラグの値を0にする命令がcld命令、1にする命令がstd命令です。

ディレクションフラグの値を変更したままret命令でHSPに戻ると、HSPが正常に動作しなくなる場合があります。 従って、ディレクションフラグの値を変更する場合は、他のレジスタと同様フラグレジスタの値をスタックに退避しなければなりません。フラグレジスタの値をスタックへ退避する命令がpushf命令、スタックに退避されている値をフラグレジスタへ戻す命令がpopf命令です。

ecxレジスタに繰り返しの回数を設定し、movs命令又はstos命令の前にrep命令を置くことで、movs命令又はstos命令を繰り返す事ができます。 rep命令は、ecxレジスタの値が0でなければ後続のストリング操作命令を実行してecxレジスタの値を1減らし、ecxレジスタの値が0になるまで繰り返します。

最高何回まで繰り返すかをecxレジスタに設定し、cmps命令又はscas命令の前にrepe命令またはrepz命令を置くことで、cmps命令またはscas命令を等しい間(等しくなくなるまで)繰り返す事ができます。 repe命令とrepz命令は全く同じ命令なので、どちらを使っても構いません。

最高何回まで繰り返すかをecxレジスタに設定し、cmps命令又はscas命令の前にrepne命令またはrepnz命令を置くことで、cmps命令またはscas命令を等しくない間(等しくなるまで)繰り返す事ができます。 repne命令とrepnz命令は全く同じ命令なので、どちらを使っても構いません。

6.3 stosb命令とlodsb命令の使用例

stosb命令とlodsb命令を使った簡単な例として、HSPのstrmid命令と同じ働きをするstringmid命令を作ってみました。 但し、HSPのstrmid命令と違って、p3を-にする事はできません。(手抜きです。)

まず、取り出した文字列を格納する変数のアドレスをediレジスタへ、取り出す元の文字列が格納されている変数のアドレスをesiレジスタに代入します。 次に、p3の値(取り出し始めのインデックス)をesiレジスタに加え、p4の値(取り出す文字数)をecxレジスタに代入します。 次に、文字列の先頭から順に取り出すためにcld命令を実行しておきます。 これで文字列を取り出すための下準備はOK。 次の行から、実際に文字列を取り出していきます。

まず、lodsb命令で最初の1文字を取り出します。 次に、取り出した値が0かどうかを調べているのは、取り出す元の文字列がそこで終わっているかどうかを判別するためです。 元の文字列がそこで終わっていれば、取り出すのをそこで終了します。 元の文字列がまだ続いていればstosb命令を実行し、[edi]に格納します。 さらにecxレジスタの値を1減らします。 ecxレジスタの値を1減らした結果が0でなければ、次の文字を取り出します。

ここで、「あれ?」と思われた方も多いかと思います。 cmp命令で比較しているわけでもないのにjnz命令を使っていますね。 実は、inc命令やadd命令等の算術演算命令や、and命令やor命令等の論理演算命令の直後にjnz命令を使うと、演算の結果が0でないときにジャンプさせる事ができるのです。 逆に、jz命令を使えば演算の結果が0の時にジャンプさせる事ができます。

文字列の終わりに到達するか、ecxレジスタの値が0になったら、文字列の終わりを示す0を[edi]に格納し、ret命令でHSPに戻ります。 この時、eaxレジスタには0が入っているので、HSPのシステム変数statの値は0になります。

STRINGMID.ASM
.486
.model          flat,stdcall
.code

DLLMain         proc    p1,p2,p3
                mov     eax,1
                ret
DLLMain         endp

stringmid       proc    export uses ecx esi edi p1:ptr,p2:ptr,p3,p4
                pushf
                mov     edi,p1
                mov     esi,p2
                add     esi,p3
                mov     ecx,p4
                cld
        @@:     lodsb
                cmp     al,0
                jz      @f
                stosb
                dec     ecx
                jnz     @b
        @@:     mov     eax,0
                mov     [edi],al
                popf
                ret
stringmid       endp

end             DLLMain
STRINGMID.AS
#uselib "stringmid.dll"
#func stringmid stringmid 5

x="123456789"
strmid y,x,3,4
print y
stringmid y,x,3,4
print y
stop
strmid

STRINGMID.ASの実行結果
4567
4567






6.4 repne命令とscasb命令の使用例

repne命令とscasb命令を使った簡単な例として、HSPのstrlen命令と全く同じ働きをするstringlength命令を作ってみました。 repne命令とscasb命令をうまく使う事で、簡単に文字列の長さを求める事ができます。

まず、文字列の長さを調べたい変数のアドレスをediレジスタに代入します。 次に、eaxレジスタに0を、ecxレジスタに-1を代入しておきます。 次に、文字列の先頭から順に調べるためにcld命令を実行しておきます。

次にscasb命令実行し、取り出した値が0(文字列の終わり)になるまで繰り返すわけですが、ここでまた「あれ?」と思われた方も多いかと思います。 先程ecxレジスタに代入した値は-1でしたよね? ecxレジスタにセットする値は、繰り返しの回数だったはずでは・・・

ここで、rep命令の説明を思い出してください。 ecxレジスタの値が0でなければ後続の命令を実行し、ecxレジスタの値を1減らすんでしたよね? つまり、最初にecxレジスタの値を-1にしているという事は、ecxレジスタの値が-2, -3, -4,…と変化していく事になります。 scasb命令が1度だけしか実行されなければecxレジスタの値は-2になり、2回実行されれば-3になるわけです。 ということは、文字列の長さが0であればecxレジスタの値は-2になり、1であればecxレジスタの値は-3になる事になりますね。 「repne scasb」を実行後のecxレジスタの値に2を加え、符号を反転すれば、文字列の長さを求める事ができます。

STRINGLENGTH.ASM
.486
.model          flat,stdcall
.code

include         debug.asm

DLLMain         proc    p1,p2,p3
                mov     eax,1
                ret
DLLMain         endp

stringlength    proc    export uses ecx edi p1:ptr,p2:ptr,dummy1,dummy2
                pushf
                mov     edi,p2
                mov     eax,0
                mov     ecx,-1
                cld
                repne   scasb
                add     ecx,2
                neg     ecx
                mov     edi,p1
                mov     [edi],ecx
                popf
                ret
stringlength    endp

end             DLLMain
STRINGLENGTH.AS
#uselib "stringlength.dll"
#func stringlength stringlength 5

x="123456789"
strlen y,x
print y
stringlength y,x
print y
stop

STRINGLENGTH.ASの実行結果
9
9







6.5 おわりに

この章ではHSPの標準命令と同じような事しかやっていないので、「一体何の役に立つんだ?」と聞かれても困るのですが、次の章ではストリング操作命令を利用した画像処理について説明したいと思います。 次の章へ進む前に、この章の内容はしっかり理解しておいてください。