9.3 KiB
楽しいプログラミング 《第4回》
年末に待望の雪が降って、まわりの話題はスキー一色。私のように滑った事のない者にとっては、肩身の狭い事この上ありません。一度行ってみようかなぁ。でも、指が霜焼けになってキーボードが叩けなくなったらヤだなぁ。
■ アセンブラ使ってます?
高級言語からプログラムを始められた方の目に、アセンブラってどういうふうに映るんでしょう。やはり多少なりともコンプレックスは感じるのでしょうね。私の場合、逆にCを始める時にかなりの抵抗がありました。慣れてしまえばあんなに楽なものはないのですが。書く、書かないは別にしても、アセンブラにコンプレックスがあるようでは、いいプログラムは作れません。ここで延々、アセンブラの効用を説いてもよろしいのですが、本書のタイトルはあくまで「Cジャーナル」。というわけで、今回は私が愛用しているアセンブラの便利マクロを、いくつかご紹介するにとどめます。
Cのライブラリを書く場合は、TASMが最適です。OPTASMはMASM5.1の高級言語サポートに対応していませんので、かっこよく引数やローカル変数を書けません。いずれにしろ、MASMを使う理由はありません。そもそも、ラディカルなプログラマが、Mと名のつく開発ツールの速度に耐えられるはずはないのです。
■ clrとtstがない
それまでずっと68系のMPUに馴染んできて、初めて86の命令コード表を眺めた時、「clr
」と「tst
」が無いのには我が目を疑いました。対レジスタでは、「clr
」を「xor
」で、「tst
」を「or
」で代用できる事を知り、なるほどと思いましたが、そんなふうに本来の動作とは異なった記述はすべきではありません。きちんと「clr
」、「tst
」と書きましょう(リスト1)。
[リスト1]
clr macro reg
xor reg,reg
endm
tst macro reg
or reg,reg
endm
tstb macro label
cmp byte ptr label,0
endm
tstw macro label
cmp word ptr label,0
endm
■ 非力なジャンプ命令
86のジャンプ命令は、前方への無条件ジャンプを明示的に「short」としないと2バイト命令にならない、ロングの条件ジャンプがない、等の不満があります。TASM2.0やOPTASMでは、このあたりを自動的に対応するよう強化されていますから、それに頼ってもよいのですが、私はリスト2のようなマクロを利用してきました。「jmpl z,label
」、「jmpln z,label
」のように使います。また、「jmpw
」は286,386対応のウェイト用です。
[リスト2]
jmps macro label
jmp short label
endm
jmpl macro cc,label
local next
jn&cc next
jmp label
next:
endm
jmpln macro cc,label
local next
j&cc next
jmp label
next:
endm
jmpw macro
local next
jmp short next
next:
endm
■ 複数レジスタのpush、pop
これができないのも目が点でした。アスキーの黒本第2弾、「MS-DOSプログラマーズハンドブック」様々です(リスト3)。「pushm <ax,bx>
」のようにブラケットでくくってレジスタを指定します。OPTASMは気が効いてて、最初から「pushm,popm
」命令が使えますから、ブラケットでくくる必要がありません。TASM2.0でも「push ax bx
」と書けるようになりましたが、レジスタをスペースで区切る(カンマは使えない)のは、きもち悪いです。
[リスト3]
pushm macro regs
irp reg,<regs>
push reg
endm
endm
popm macro regs
irp reg,<regs>
pop reg
endm
endm
■ 複数回のレジスタシフト
186以降で「shr reg,imm
」の命令がサポートされました。「今どき8086なんて無視無視」、と言い切れる人はそのまま書いてしまえばよいのですが、少々はばかられる人はリスト4のようなマクロを使いましょう。「@Cpu
」はTASMの定義済みシンボルで、「.186
」疑似命令で指定したプロセッサの型を表します。
[リスト4]
$shift macro op,reg,imm
if @Cpu and 2
op reg,imm
else
rept imm
op reg,1
endm
endif
endm
shlm macro reg,imm
$shift shl,reg,imm
endm
shrm macro reg,imm
$shift shr,reg,imm
endm
■ システムコール
MS-DOSやBIOSのシステムコールも、当然マクロを使います。ALレジスタも使うものは、まとめてAXレジスタへロードできるようにしましょう。そういう場合に便利な「'movhl'」マクロも用意しておきます(リスト5)。ファンクション番号もシンボル定義すると、いっそう読みやすくなります。
F_OPEN equ 3Dh
F_CLOSE equ 3Eh
O_READ equ 0
msdos F_OPEN,O_READ
...
msdos F_CLOSE
[リスト5]
movhl macro rx,rh,rl
mov rx,(rh shl 8)+(rl and 0FFh)
endm
msdos macro cmd,prm
ifb <prm>
mov ah,cmd
else
movhl ax,cmd,prm
endif
int 21h
endm
■ 構造化マクロ
アセンブラの「構造化マクロ」というと、高級言語風にインデントまでしてしまい、マクロを理解していないととても読めないようなものまでありますが、私が使っているものはもっと素朴な、「IF~THEN~ELSE~」とループだけのものです(リスト6)。それでもローカルラベルが大幅に減り、可読性と記述性は格段に向上します。そもそも、アセンブラを使う限り、ソースから生成コードがただちにイメージできないまでに「構造化」する必要などありません。それならCとインラインアセンブラで書けば良いのですから。
使い方の例をリスト7に示します。ネストの深さ、ネスト内で並列に書ける条件ブロックの数に制限がありますが、かなり以前に書いたマクロで、ドキュメントも残っていないため、正確なところはわかりません。それらの制限を越えるとアセンブルエラーとなりますから、その場合は止むを得ずローカルラベルを使いましょう。普通の素直なプログラムであれば、まず大丈夫です。
[リスト6]
$base = 0
$field = 1
$add = 1
$nest = 0
_if macro cc
$begin
$jcc n,cc,%$base,%$field
endm
_ifn macro cc
$begin
$jcc <>,cc,%$base,%$field
endm
_else macro
$end
$jmp %$base,%($field+1)
$label %$base,%$field
$field = $field+1
endm
_endif macro
$end
$label %$base,%$field
$end2
endm
_repeat macro
$begin
$label %$base,%$field
endm
_until macro cc
$end
$jcc n,cc,%$base,%$field
$label %$base,%($field+1)
$end2
endm
_while macro cc
$end
$jcc <>,cc,%$base,%$field
$label %$base,%($field+1)
$end2
endm
_loop macro
$end
$loop %$base,%$field
$label %$base,%($field+1)
$end2
endm
_break macro cc
$jcc <>,cc,%$base,%($field+1)
endm
_cont macro cc
$jcc <>,cc,%$base,%$field
endm
$begin macro
$field = $field shl 1
$nest = $nest + 1
endm
$end macro
if ($nest le 1)
$add = 1
$base = $base mod 128
else
$add = 8 shl ($nest * 2)
$base = $base mod ($add * 4)
endif
endm
$end2 macro
$base = $base + $add
$field = $field shr 1
$nest = $nest - 1
endm
$jmp macro l1,l2
jmp short $L&l1&_&l2
endm
$jcc macro n,cc,l1,l2
ifnb <cc>
j&n&cc $L&l1&_&l2
else
$jmp l1,l2
endif
endm
$loop macro l1,l2
loop $L&l1&_&l2
endm
$label macro l1,l2
$L&l1&_&l2:
endm
[リスト7]
; _if <cond> ... _else ... _endif
; _ifn <cond> ... _else ... _endif
cmp al,1
_if b
...
_else
cmp al,3
_ifn e
...
_else
...
_endif
_endif
; _repeat ... _until <cond>
; _repeat ... _while <cond>
; _repeat ... _loop
mov dx,25
_repeat
...
mov cx,80
_repeat
...
_loop
...
dec dx
_until z
; _repeat ... _break <cond> ... _until
; _repeat ... _cont <cond> ... _until
_repeat
...
cmp al,'A'
_break e
cmp al,'a'
_break e
tst al
_cont z
...
_until
アセンブラも、このように意図している動作をコードから自然に読み取れるような記述をすれば、決して難しいものではありません。アセンブラの難しさは、可読性というよりはむしろ、レジスタの内容、フラグ、スタックレベル等を常に注意深く把握しておかなくてはならない点にあります。
次回はがらりと趣向を変えて、Turbo Pascal6.0のTurbo Visionでいってみましょう。(こう言っておけば、読まざるを得ないですから)
(雑誌「PC POWER 1992年10月号」掲載)