CodeZine(コードジン)

特集ページ一覧

MASM32によるアセンブラ入門:パート3

MASM32のマクロ

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/04/19 00:00

いよいよこのチュートリアルの最終回です。この第3回では、一般的な算術関数と、アセンブラを実装するうえで大きな助けになるMASM32のマクロをいくつか紹介したいと思います。

お読みになる前に

 本稿の内容は、読者の環境にMASM32がインストールされていることを前提にしています。まだインストールしていない場合は、http://www.masm32.com/から入手してください。

はじめに

 いよいよこのチュートリアルの最終回です。この第3回では、一般的な算術関数と、アセンブラを実装するうえで大きな助けになるMASM32のマクロをいくつか紹介したいと思います。

増分と減分

 増分と減分の処理はinc命令とdec命令で行います。次に例を示します。

TestProc proc

mov eax, 5

dec eax
dec eax

mov dl, 10
inc dl
inc dl

ret

TestProc endp

 dec命令で減分を行うと、結果がゼロになったときにゼロフラグがセットされます。これを利用してループを実装することができます。次に例を示します。

TestProc proc

   mov ecx, 10
   xor eax, eax    ; efficient way of saying eax=0

LoopStart:
   inc eax
   dec ecx
   jnz LoopStart

   ; eax now equals 10

   ret

TestProc endp

加算と減算

 加算と減算はadd命令とsub命令で行います。これらの命令の基本的な構文は次のとおりです。

add/sub (destination), (source)

 レジスタとレジスタを加算するほかに、定数とレジスタを加算したり、メモリの内容とレジスタを加算したりすることができます。sourcedestinationは同じサイズ(ビット数)でなければなりません。どちらの命令も、システムのフラグに影響を与えます。たとえばsub命令の結果がゼロの場合は、ゼロフラグがセットされます。次に例を示します。

AddValues proc dwValue1:DWORD, dwValue2:DWORD

   mov eax, dwValue1
   add eax, dwValue2
   ret

AddValues endp

 このメソッドでは、渡された2つの値を加算し、結果を返します。

乗算と除算

 乗算と除算はmul命令とdiv命令で行います。これらの命令はアキュムレータレジスタ(eax)に対してのみ処理を行い、データレジスタ(edx)はオーバーフローとして使用します。レジスタのどの部分が影響を受けるかは、オペランドのサイズによって決まります。

 次の図は、この2つの命令でアキュムレータレジスタとデータレジスタが使用されるときに、これらのレジスタがどのように組み合わされるかを示しています。

 したがって、予想どおりの結果を得るためには、mulまたはdivを呼び出す前にedxをゼロに設定することをお勧めします。次に例を示します。

TestProc proc

   mov eax, 10
   xor edx, edx    ; set edx to zero

   mul 10
   div 10

   ret

TestProc endp

論理演算

 通常の論理演算はorandxor命令で行います。これらの命令の構文は次のとおりです。

logical operation (destination), (source)

 sourcedestinationは同じサイズ(ビット数)でなければなりません。次に例を示します。

LogicalFunction proc

   xor eax, eax    ; the efficient way of saying eax=0

   mov ax, 100
   mov bx, 5
   and ax, 1
   or ax, bx
   ret

LogicalFunction endp

ビットシフト演算

 shl命令とshr命令は、与えられたレジスタビットを指定のビット数だけ左または右にシフトします。この2つの命令は非常に効率的なので、2の累乗のパラメータに関しては、mul命令やdiv命令よりもこちらを使用した方がよいでしょう。次に例を示します。

ShiftFunction proc

   mov eax, 1
   shl eax, 2    ; shift eax's bits left 2 times :  i.e. eax *= 4
   shr eax, 2    ; shift eax's bits right 2 times : i.e. eax /= 4

   ret

ShiftFunction endp

テスト命令とループ

 特定の条件をテストするために使用できる命令は数多くあります。これらの命令は算術演算と同じ演算を行いますが、レジスタ内の値を変更せず、フラグだけを設定します。

 たとえばcmp命令はdestinationからsourceを効率的に減算しますが、結果の値を保存しません。次に例を示します。

CmpFunction proc

   mov eax, 100
   cmp eax, 100

   ; jump if equals
   je Equals

   ; not equal
   mov eax, 2
   jmp EndIf

Equals:
   mov eax, 1

EndIf:
   ret

CmpFunction endp

 test命令は、sourceオペランドとdestinationオペランドに対してand演算を実行し、その結果に従ってフラグをセットします(結果は保存しません)。

 loop命令はecxを1ずつ減分し、結果がゼロでない場合は指定の場所にジャンプします。

LoopFunction proc

   xor eax, eax
   mov ecx, 10

LoopStart:
   inc eax
   loop LoopStart

   ret

LoopFunction endp

MASM32のマクロ

 MASM32には、アセンブラ開発者の役に立つマクロが多数用意されています。以降ではそのごく一部を紹介したいと思います。

 1つ目は.if文です。これにより、2つのオペランドを標準のC++演算子(=>=<=など)を使用して比較することができます。

IfProc proc

   mov eax, 100
   mov ecx, 200

   .if eax == ecx
      ; do something
   .else
      ; do something else
   .endif

   ret

IfProc endp

 2つ目は.repeat~.untilループです。このループにはさまざまな形式があります。.untilcxzは、ecxを1ずつ減分し、結果がゼロでない場合はループを続行します。.until zero?は、ゼロフラグがセットされるまでループを続行します。

LoopProc proc

   xor eax, eax
   mov ecx, 100

   .repeat

      inc eax

   .untilcxz

   ret

LoopProc endp

 ループ内でループを実行するときは、レジスタeaxebxedxを自由に使用するために、外側のループのecx値をプッシュ(push)しておき、内側のループを抜けたときにポップ(pop)することができます。次に例を示します。

LoopInLoopProc proc

   xor eax, eax
   mov ecx, 100

   .repeat

      push ecx
      mov ecx, 100

      .repeat

         inc eax

      .untilcxz

      pop ecx

   .untilcxz

   ret

LoopInLoopProc endp

アセンブラ内から関数を呼び出す

 アセンブラコード内から関数を呼び出すにはinvokeを使用します。invokeの後に関数名を指定し、パラメータリストをカンマ区切りで指定します。次に例を示します。

Function1 proc dwValue:DWORD

   add eax, 100
   ret

Function1 endp

MainFunction proc

   mov eax, 100

   invoke Function1, eax

   ; eax now = 200, i.e. eax += 100
   ret

MainFunction endp

 関数名と最初のパラメータの間にカンマを指定することに注意してください。

ローカルメモリ

 MASM32では、関数にローカルなメモリを割り当て、このメモリにラベルを付けることができます。これはローカル変数と同じように考えられますが、対応するマシン語コードを分析してみると、メモリアクセスの変形にすぎないということがわかります。

 ローカルメモリは関数の冒頭で定義します。マシン語コードを分析してみると、実際には、関数内の最初の命令の前に静的なメモリブロックを割り当てていることがわかります。このメモリのサイズは、MASM32の基本型(BYTEWORD、またはDWORD)によって決定されます。

ExampleLocalMemory proc

   LOCAL dwValue:DWORD    ; allocates 4 bytes and labels it 'dwValue'
   LOCAL wValue:WORD      ; allocates 2 bytes and labels it 'wValue'
   LOCAL bValue:BYTE      ; allocates 1 byte  and labels it 'bValue'

   xor eax, eax

   mov dwValue, eax
   mov wValue, ax
   mov bValue, al

   ret

ExampleLocalMemory endp

最適化

 効率的なコードを書くためには、すべての命令に同じ時間がかかるわけではないということを理解する必要があります。たとえばmul命令やdiv命令は、shr命令やshl命令のようなビットシフト演算よりも時間がかかります。それぞれの演算にどのくらいの時間がかかるかは、MASM32のヘルプファイルに記載されています。

 効率的なコードを書くためにもう1つ注意しなければならないのは、ループ内に含まれる命令の数です。命令数が少なければ少ないほど、コードの実行速度は速くなります。

 メモリへのアクセスはレジスタへのアクセスよりも遅いので、コードを記述するときは、関数のローカルメモリよりもレジスタを使用するように努めてください。

 また、jmp命令の速度はジャンプするバイト数によって左右されます。jmp命令は8、16、または32ビットのオフセットを取り、32ビットのジャンプよりも8ビットのジャンプの方がずっと高速です。ループについても同じことが言えます。命令のサイズが128バイト未満のループは、長いコードブロックを含むループよりも効率的に実行されます。

 最も重要なのはアルゴリズムそのものです。高速なアルゴリズムとは、単純なアルゴリズムです。アルゴリズムが単純であれば、必要な命令の数も少なくなるからです。特定のタスクに使用しているアルゴリズムを見直すことが大切です。ある程度の正確性または柔軟性を犠牲にすることで大幅な速度向上が見込める場合は、速度の方を優先すべきです。

 アセンブラコードの最適化については、この他にも検討すべき点が数多くあります。コードの微調整に関しては、MASM32のヘルプファイルが大いに役立つでしょう。

まとめ

 今回のシリーズはほんの導入であり、これですべてがわかるというものではありません。もっと詳しく知りたい方は、MASM32に付属しているチュートリアルとヘルプファイルを参照してください。

 このシリーズを通じて、アセンブラのコーディングは難しいものではないと理解していただければ嬉しく思います。ぜひアセンブラを使用して、自作のアプリケーションの高速化や、リアルタイム処理の実現に挑戦してみてください。

 この全3回のチュートリアルが皆さんのお役に立てば幸いです。

 パート1へ→



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • darwen(darwen)

    Windows 3.11のVisual C++/MFC 1.5の時代からWindowsプログラミングに取り組み始める。現在は、多言語展開を図っているとあるソフトウェア会社のベテラン開発者としてあらゆるプロジェクトに従事。暇さえあればプログラミングをしているのではないかという噂あり。

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5