Yoshihiko Hyodo 6f06b9514a first commit
2024-11-18 22:21:26 +09:00

9.3 KiB
Raw Blame History

楽しいプログラミング 《第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」は286386対応のウェイト用です。

[リスト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弾、「MSDOSプログラマーズハンドブック」様々ですリスト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

■ システムコール

MSDOSや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

■ 構造化マクロ

アセンブラの「構造化マクロ」というと、高級言語風にインデントまでしてしまい、マクロを理解していないととても読めないようなものまでありますが、私が使っているものはもっと素朴な、「IFTHENELSE」とループだけのものですリスト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月号」掲載