为了提高编程语言的灵活性,MASM6.0的新改动使得汇编语言越来越接近高级语言,已经初步具有了高级语言的一些常见的伪指令。
目录
高级语言结构特性
分支控制伪指令
MASM6.0引入了.IF,.ELSEIF,.ELSE和.ENDIF伪指令,类似于高级语言的if,then,else和endif的相应功能。除了高级语言中常见的操作符,汇编语言还有一些针对标志的判断条件,具体如下:
- CARRY? :判断CF是否为1,当CF=1为真
- OVERFLOW? :判断OF是否为1,当OF=1为真
- PARITY? :判断PF是否为1,当PF=1为真
- SIGN? :判断SF是否为1,当SF=1为真
- ZERO? :判断ZF是否为1,当ZF=1为真
下面是一个简单的采用条件控制伪指令编写的双分支结构的源程序段:
.if ax==5
mov bx,ax
mov ax,0
.else
dec ax
.endif
经过汇编展开后,汇编程序会自动补全,一下是补全之后的程序(其中*表示补全的语句):
.if ax==5
*cmp ax,05h
*jne @c0001
mov bx,ax
mov ax,0
.else
*jmp @c0003
*@c0001:dec ax
.endif
*@c0003:
可以看到,补全之后的指令和原来预定实现的功能是等价的。
数据定义伪指令
数据定义伪指令db,dw,dd分别表示字节,字和双字变量,但是在实际运算时,汇编程序会统一把这些变量当作无符号数(符号拓展指令和强制有符号数运算指令除外),在进行比较时,如果需要进行有符号数的比较,则需要将变量声明为有符号数。这个时候可以使用SBYTE,SWORD和SDWORD伪指令。当比较对象是寄存器数据或者立即数时,可以使用SBYTE PTR和SWORD PTR声明。
循环控制伪指令
MASM6.0提供了.WHILE,.ENDW的类似WHILE循环的伪指令,以及.REPEAT和.UNTIL,.UNTILCXZ(直到CX==0)的类似FOR循环的伪指令。同时,也提供了.BREAK和.CONTINUE伪指令,他们的作用和高级语言中的break和continue是相同的。
下面提供使用WHILE和使用.REPEAT的两个例子。
设ARRAY是一个100个字元素的数组,计算其中前若干个非负数的和,直到出现第一个负数为止。本程序采用.REPEAT+.UNTILCXZ结构
.model small
.data
arrau sword 100 dup(?)
result sword ?
.code
.startup
mov cx,100
xor ax,ax
lea bx,array
.repeat
.if sword ptr [bx]>=0
add ax,[bx]
adc dx,0 ;dx.ax+=bx
.else
.break
.endif
inc bx
inc bx ;bx<-bx+2,因为是字单元
.untilcxz
mov result,ax
.exit 0
end
;上述程序类似为:
;ax=0;
;for (bx=0;array[bx]>=0;bx++)
; ax+=bx;
子程序声明和调用伪指令
MASM6.0中,子程序声明原型和子程序调用可以使用PROTO和INVOKE伪指令。其基本格式如下:
; 过程名 proto [调用距离][语言类型][, 参数:[类型]]...
proce proto c, a:word,b:word
;invoke 过程名[,参数]...
invoke proce ax,bx
其中关于语言类型有如下规定:
语言类型 | C | SYSCALL | STDCALL | Pascal | BASIC | FORTRAN |
命名约定 | 下划线 | 下划线 | 大写名称 | 大写名称 | 名字大写 | |
参数传递顺序 | 从右到左 | 从右到左 | 根据情况 | 从左到右 | 从左到右 | 从左到右 |
平衡堆栈 | 调用程序 | 被调用 | 被调用 | 被调用 | 被调用 | 被调用 |
保存 | BP | 是 | 是 | 是 | ||
允许 | VARARG | 是 | 是 | 是 |
下面是一个使用子程序声明和调用伪指令的程序,result保存数组array的校验和:
.model small
checksumd proto c, :word, :word ;过程原型
.stack
.data
count equ 10
array db 12h,15h,0f0h,0a3h,3,68,71,0cah,0ffh,90h
result db ?
.code
.startup
invoke checksumd, count,offset array
mov result, al
.exit 0
checksumd proc c uses bx cx,countp:word,arrayp:word ;过程定义
mov bx,arrayp
mov cx,countp
xor al,al
sumd:add al,[bx]
inc bx
loop sumd
ret
checksumd endp
end
子程序的伪指令定义实际上是利用堆栈传递参数,例如,子程序经过汇编语言展开后的列表文件如下:
invoke checksumd, count, offset array
* mov ax,word ptr offset array
* push ax
* mov ax,+000ah
* push ax
* call checksumd
* add sp,04h
...
checksumd proc c uses bx cx,countp:word, arrayp:word
* push bp
* mov bp,sp
* push bx
* push cx
mov bx,arrayp
mov cx,countp
xor al,al
sumd:add al,[bx]
inc bx
loop sumd
ret
* pop cx
* pop bx
* pop bp
* ret 000h
checksumd endp
宏结构程序设计
宏是另外一种简化源程序的方法。通常与宏汇编配合的指令还有重复汇编和条件汇编,统称为宏结构。
宏汇编
在C中常见到#define的宏定义指令,在汇编中使用MACRO关键字来定义一个宏。经过定义的宏,只需要写出宏名,就可以在汇编语言中自动展开,和C相同。其基本格式如下:
;name macro[,参数表]
body
endm
宏定义中的注释如果用;;分隔,则后面的宏展开中不出现该注释。
可以将常用的一些程序段定义为宏,例如:
begin macro
mov ax,datas
mov ds,ax
endm
;调用时
begin
return macro retnum
mov al,retnum
mov ah,4ch
int 21h
endm
;调用时
return 0
print macro msg
lea dx,offset msg
mov ah,9
int 21h
endm
;调用时
print msg
C++的inline函数和该想法有点相似。
宏必须先定义后调用,调用时需要在宏名后方跟随实参表。
下面是利用宏汇编使用信息显示的示例程序:
.model small
.stack
.data
string db 'Hello, world!', 13,10,'$'
.code
start:begin
print string
return 0
end start
串传参
当传递给宏的参数是一个字符串时,需要涉及格式化、转义等等特殊伪指令助记符,汇总如下:
- <>用于将字符串参数括起来。
- !转义符
- &a& 说明a是一个变量而不是一个字符。
- %(exp)表示exp是一个表达式,返回该表达式的值。
- ;;表示是宏定义的注释。
- :=value可以跟在参数后方表示默认值。
其他伪指令
- LOCAL用于声明宏内部的标号,用于宏内部语句跳转使用
- PURGE可以在不需要某个宏定义时删除它。
- EXITM可以立刻退出宏展开并且进入后面的语句,类似于子程序的RET指令。
下面是一个例子:
absol macro oprd
local next
cmp oprd,0
jge next
neg oprd
exitm
next:
mov ax,oprd
endm
重复汇编
程序中有时候需要连续重复一段语句,可以使用重复汇编伪指令。重复汇编伪指令是将相同的汇编指令展开固定次数,和循环控制伪指令有区别。其基本格式如下:
repeat 重复次数
重复体
endm
for 形参,<实参表>
重复体
endm ;将实参表轮流带入形参执行重复体
forc 形参,字符串
重复体
endm ;将字符串中字符轮流带入形参执行重复体
条件汇编
条件汇编伪指令类似C中的#ifdef 指令,根据某种条件确定是否汇编某段语句。其基本格式如下:
if exp
分支1
[else
分支2]
endif
除了IF条件汇编之外,还有IFE(表达式是否为0),IFDEF(是否已定义符号),IFNDEF(是否未定义符号),IFB(是否存在该宏定义),IFNB,IFIDN<str1>,<str2>(若字符串1等于字符串2,区分大小写),IFIDNI<str1><str2>(若字符串1等于字符串2,不区分大小写)。
条件汇编伪指令和条件控制伪指令不同,条件汇编伪指令在程序执行之前就已经确定了执行哪一个分支。
总结
宏定义和控制伪指令都是高级语言中十分常见的特性,而MASM6.0使得汇编语言也可以通过伪指令使用其中的一部分。