作为汇编语言的课程笔记,方便之后的复习与查阅
本篇为课程第十和第十一次课内容
目录
汇编语句格式
标识符
- 标识符(Identifier)一般最多由31个字母、数字及规定的特殊符号(如 _、$、?、@)组成,不能以数字开头。默认情况下,汇编程序不区别标识符中的字母大小写
- 一个程序中,每个标识符的定义是唯一的,还不能是汇编语言采用的保留字。
- 保留字(Reserved Word)是汇编程序已经利用的标识符,主要有:
硬指令助记符——例如:MOV、ADD
伪指令助记符——例如:DB、EQU
操作符——例如:OFFSET、PTR
寄存器名——例如:AX、CS
硬指令、执行性语句
硬指令:使CPU产生动作、并在程序执行时才处理的语句
执行性语句——由硬指令构成的语句,它通常对应一条机器指令,出现在程序的代码段中,格式:
标号: 硬指令助记符 操作数,操作数 ;注释
again: mov dx, offset string ;注释
- 标号:反映硬指令位置(逻辑地址)的标识符,后跟一个冒号分隔
- 硬指令助记符:可以是任何一条处理器指令
- 处理器指令的操作数可以是立即数、寄存器和存储单元
伪指令、说明性语句
伪指令(Directive)——不产生CPU动作、在程序执行前由汇编程序处理的说明性语句
伪指令与具体的处理器类型无关,但与汇编程序的版本有关
说明性语句——由伪指令构成的语句,它通常指示汇编程序如何汇编源程序:
名字 伪指令助记符 参数,参数,… ;注释
string db ‘Hello,world’ ;注释
- 名字:反映伪指令位置(逻辑地址)和属性的标识符,后跟空格或制表符分隔,没有冒号
- 伪指令助记符:定义字节数据和字符串的
DB
就是伪指令 - 伪指令的参数可以是常数、变量名、表达式等,可以有多个,参数之间用逗号分隔
变量定义
变量定义(Define)伪指令为变量申请固定长度的存储空间,并可同时将相应的存储单元初始化
变量定义伪指令要放在.exit
后,end
之前,因为它不是可执行的语句
变量名
变量名为用户自定义标识符,表示初值表首元素的逻辑地址;用这个符号表示地址,常称为符号地址。设置变量名是为了方便存取它指示的存储单元。变量名可以没有。这种情况,汇编程序将直接为初值表分配空间。
初值表
初值表是用逗号分隔的参数
主要由数值常数、表达式或?
、DUP
组成
?
——表示初值不确定,即未赋初值
BUFFER DB ?
DUP
——表示重复初值
DUP
的格式为:重复次数 DUP(重复参数)
BUFFER DB 10 dup(0)
BUFFER DB 10 dup(?)
变量定义伪指令助记符
DB
——定义字节伪指令
DW
——定义字伪指令
DD
——定义双字伪指令
DF——定义3字伪指令
DQ——定义4字伪指令
DT——定义10字节伪指令
重点掌握前三个
定义字节单元伪指令DB
DB
伪指令用于分配一个或多个字节单元,并可以将它们初始化为指定值
初值表中每个数据一定是字节量(Byte),存放一个8位数据:
- 0~255的无符号数
- -128~+127带符号数
- 字符串常数
X db 'a',-5
db 2 dup(100),? ;第二行没有带名字,默认由x往下继续进行内存分配
Y db 'ABC'
定义字单元伪指令DW
初值表中每个数据一定是字量(Word),一个字单元可用于存放任何16位数据:
- 一个段地址
- 一个偏移地址
- 两个字符
- 0~65535之间的无符号数
- -32768~+32767之间的带符号数
count dw 8000h,?,'AB' ; A在高地址处
maxint equ 64h ; equ是等价的意思,表示之后maxint就是64h
number dw maxint
array dw maxint dup(0)
定义双字单元伪指令DD
初值表中每个数据是一个32位的双字量(Double Word):
- 有符号或无符号的32位整数
- 用来表达16位段地址(高位字)和16位的偏移地址(低位字)的远指针
vardd DD 0,?,12345678h
farpoint DD 00400078h
PTR操作符
PTR
操作符使名字或标号具有指定的类型
- 类型名可以是
BYTE
/WORD
/DWORD
/FWORD/QWORD/TBYTE
mov byte ptr [2000H],10H
使用PTR
操作符,可以临时改变名字或标号的类型
顺序程序设计
求两数之和
NUM1和NUM2单元中各存放着一个双字无符号数,编制程序计算两数之和,将结果存于SUM双字单元中,进位保存到FSUM字节单元中
- 分析:最多一次处理16位,双字需分开处理
.model tiny
.code
.startup
mov ax,word ptr num1 ;处理低16位
add ax,word ptr num2
mov word ptr sum,ax ;保存低16位
mov ax,word ptr num1+2 ;处理高16位
adc ax,word ptr num2+2
mov word ptr sum+2,ax ;保存高16位
mov dx,0 ;最高进位
rcl dl,1
mov fsum,dl ;保存进位
.exit 0
num1 dd 82348567H
num2 dd 87658321H
sum dd ?
fsum db ?
end
也可以用串操作
移位
ddvarw为一个32位的数据12ab56cdH,编程将其整个循环左移4位(使用顺序结构)得到 2ab56cd1H
.model tiny
.code
.startup
mov ax,word ptr ddvar ;低16位
mov dx,word ptr ddvar+2 ;高16位
mov cl , 4
mov bh , dh ;保存高8位到BH
mov bl , ah ;保存低8位到BL
shl dx , cl ;高16位左移 4位
shl ax , cl ;低16位左移 4位
shr bh , cl ;高8位右移4位,高4位到bh中
shr bl , cl ;低8位右移 4位,低4位到bl中
or dl , bl ;原低4位转到高4位
or al, bh ;原高4位转到低4位
.exit 0
ddvar dd 12ab56cdh
end
代码转换 XLAT
用查表法,实现一个8位二进制数(00-0FH)转换为ASCII码显示
.model tiny
.code
.startup
mov bx,offset ASCII ;BX指向ASCII码表
mov al,hex ;AL取得一位16进制数,
and al,0fh ;只有低4位是有效的,高4位清0
xlat ; 换码:AL←DS:[BX+AL]
mov dl,al ;入口参数:DL←AL
mov ah,2 ;02号DOS功能调用
int 21h ;显示一个ASCII码字符
.exit 0
ASCII db 30h,31h,32h,33h,34h,35h
db 36h,37h,38h,39h ;0~9的ASCII码
db 41h,42h,43h,44h,45h,46h ;A~F的ASCII码
hex db 0bh ;任意设定了一个待转换的1位16进制数
end
分支程序设计
- 判断的条件是各种指令,如
CMP
、TEST
等执行后形成的状态标志 - 转移指令
Jcc
和JMP
可以实现分支控制
二分支
比较两个数大小:在DATA1和DATA2单元中各有一个16位二进制无符号数,找出其中较小的数,存于MIN单元中
.model tiny
.code
.startup
mov ax,data1
cmp ax,data2
jb next
mov ax,data2 ;CF=0
next:
mov min,ax ;存结果
.exit 0
data1 dw 2000h
data2 dw 3f80h
min dw ?
end
三分支
判断变量var中的值,若为正数,在result中存入1;为负数,在result中存入-1;为零,在result中存入0
.model tiny
.code
.startup
mov ax,var
cmp ax,0
jz zero ;等于0
jg great ;大于0
mov ax,0ffffh ;小于0
jmp ext
zero: mov ax,0
jmp next
great: mov ax,0001h
next: mov result,ax
.exit 0
var dw 8001h
result dw 0
end
多分支
(了解即可)
- 可用跳跃表法,使程序根据不同的条件转移到多个程序分支中去执行
需要在数据段事先安排一个按顺序排列的转移地址表。因为转移地址为16位偏移地址,所以表地址偏移量需要加2(type=2)调整。可用JMP
指令的变址寻址,或寄存器间接寻址,或基址变址寻址方式来实现跳跃表法的程序
Table dw disp1,disp2,disp3,disp4,disp5,disp6,disp7,disp8
例:根据键盘输入的1-8数字跳转到8个不同的处理程序段
.model tiny
.code
.startup
start1:
mov dx,offset msg ;9号功能显示字符串
mov ah,9
int 21h
mov ah,1 ;1号功能获取用户输入
int 21h
cmp al,'1' ;输入不是1~8,则跳转回去
jb start1
cmp al,'8'
ja start1
and ax,000fh ;设置地址偏移
dec ax
shl ax,1 ;因为是双字,所以地址偏移需要用shl来乘2
mov bx,ax
jmp table[bx]
start2:
mov ah,9
int 21h
.exit 0
disp1:
mov dx,offset msg1
jmp start2
disp2:
mov dx,offset msg2
jmp start2
……
disp7:
mov dx,offset msg7
jmp start2
disp8:
mov dx,offset msg8
jmp start2
Msg db 'Input Number(1~8):',0dh,0ah,'$'
Msg1 db 'Chapter 1!',0dh,0ah,'$'
Msg2 db 'Chapter 2!',0dh,0ah,'$'
Msg3 db 'Chapter 3!',0dh,0ah,'$'
Msg4 db 'Chapter 4!',0dh,0ah,'$'
Msg5 db 'Chapter 5!',0dh,0ah,'$'
Msg6 db 'Chapter6!',0dh,0ah,'$'
Msg7 db 'Chapter 7!',0dh,0ah,'$'
Msg8 db 'Chapter 8!',0dh,0ah,'$'
Table dw disp1,disp2,disp3,disp4,
dw disp5,disp6,disp7,disp8
end
循环程序设计
- 循环指令和转移指令可以实现循环控制
例1: 求1+2+3+…+100的和,存在sum字单元
.model tiny
.code
.startup
xor ax,ax ;被加数AX清0
mov cx,100
again:
add ax,cx ;从100,99,...,2,1倒序累加
loop again
mov sum,ax ;将累加和送入指定单元
.exit 0
sum dw ?
end
例2:把BX
中的数以十六进制的形式显示在屏幕上。比如BX
=8F6AH,屏幕显示 8F6AH
这个例子要重点理解
- 要把
bx
中数据转成ascii码 - 用循环移位,先显示高4位的16进制数,接着继续移位处理其他位
mov ch, 4 ;循环4次
rotate:
mov cl, 4 ;每次移位4位
rol bx, cl
mov al, bl ;只取bx低4位
and al, 0fh
add al, 30h ;’0’-’9’ ASCII 30H-39H
cmp al, 3ah
jl printit ;有符号数的比较
add al, 7h ;’A’-’F’ ASCII 41H-46H
printit:
mov dl, al
mov ah, 2
int 21h
dec ch
jnz rotate
例3:在数据段中从buffer单元开始存放10个16位二进制有符号数,把其中最大数找出来存于MAX单元中
.model tiny
.code
.startup
mov bx,offset buffer
mov cx,10
mov ax,[bx]
circle:
inc bx ;16位,地址一次要加2
inc bx
cmp ax,[bx]
jge next
mov ax,[bx]
next:
loop circle
mov max,ax
.exit 0
buffer dw -100,3000,-1,22,8000h
dw 9232,0,-3632,-3144,6322
max dw ?
end
例4:字符串比较——比较内存中的两个字符串string1
和string2
,字符串长度都为COUNT
个。若完全相同,则将RESULT
单元清0;不同则将RESULT单元送ffh
.model tiny
.code
.startup
mov si, offset string1
mov di, offset string2
mov cx, count
cld
repz cmpsb ;重复比较两个字符
jnz unmat ;zf=0,字符串不等,转移
mov al,0 ;zf=1,字符串相等,设置00h
jmp output ;转向output
unmat: mov al,0ffh ;设置ffh
output: mov result,al ;输出结果标记
.exit 0
string1 db ‘Hello,Everybody!’
string2 db ‘Hello,everybody!’
count equ $-string2 ;$代表count的地址,因此count就为string2字符串的个数
end
有不同字符时,SI
,DI
中分别存放第一个不相同字符的下一个地址;CX
中存放剩下还未比较的字符个数
例5:字符串搜索——从一个字符串中查找一个指定的字符T
。若找到,则将该字符存入char
单元,并记录该字符所在位置的偏移地址,存于caddr
中;若未找到,则将char
置为0ffh
(全1)。
.model tiny
.code
.startup
lea di, mess ;lea 获取一个变量的有效地址 等价于mov di, offset mess
mov al, ‘T’
mov cx, count
cld
repne scasb
jnz over ;zf=0,未找到
;zf=1,找到
mov char, al ;存字符值
dec di
mov caddr,di ;存字符地址
over: .exit
mess db ‘COMPUTER’
count equ $-mess
char db 0ffh
caddr dw ?
end
例6:将首地址为array
的字节数组从小到大排序——冒泡法
mov cx,count ;CX←数组元素个数
dec cx ;元素个数减1为外循环次数
outlp: mov dx, cx ;DX←内循环次数
mov bx, offset array
inlp: mov al,[bx] ;取前一个元素
cmp al,[bx+1] ;与后一个元素比较
jna next ;前一个不大于后一个元素,则不进行交换
xchg al,[bx+1] ;否则,进行交换
mov [bx],al
next: inc bx ;下一对元素
dec dx
jnz inlp ;内循环尾
loop outlp ;外循环尾
子程序设计
子程序的常见格式
subname proc ;具有缺省属性的subname过程
push ax ;保护寄存器:顺序压入堆栈
push bx ;ax/bx/cx仅是示例
push cx
… ;过程体
pop cx ;恢复寄存器:逆序弹出堆栈
pop bx
pop ax
ret ;过程返回
subname endp ;过程结束
子程序的参数传送
- 入口参数(输入参数):主程序提供给子程序
- 出口参数(输出参数):子程序返回给主程序
- 参数的形式:
① 数据本身(传值)
② 数据的地址(传址) - 传递的方法:
① 寄存器 ② 变量 ③ 堆栈
无参数传递的子程序
;子程序功能:实现光标回车换行
dpcrlf proc ;过程开始
push ax ;保护寄存器AX和DX
push dx
mov dl,0dh ;显示回车
mov ah,2
int 21h
mov dl,0ah ;显示换行
mov ah,2
int 21h
pop dx ;恢复寄存器DX和AX
pop ax
ret ;子程序返回
dpcrlf endp ;过程结束
通过寄存器传递参数
- 把参数存于约定的寄存器中,可以传值,也可以传址。
- 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护)
- 子程序对带有入口参数的寄存器可以保护,也可以不保护(视具体情况而定)
例1:将AL
中存放的低4位二进制数转换为相应的ASCII码子程序。
入口参数: (AL
) 存放二进制数;
出口参数: (AL
)存放ASCII码。
htoasc proc
and al,0fh
add al,30h
cmp al,3ah
jl htoasc;0~9
add al,7;A~F
htoasc1: ret
htoasc endp
例2:计算字符串长度子程序。字符串是以ASCII码值0(NULL)为结束标志的一组ASCII码字符序列
入口参数:si
为字符串起始地址
出口参数:ax
为字符串的长度
strlen proc
mov di,si
mov ax,ds
mov es,ax ;es:di指向字符串
xor al,al ;(al)清零
mov cx,0ffffh ;(cx)置初值 cx赋为ffffh=-1表示一直循环
cld
repnz scasb ;查找结束符
mov ax,cx ;cx最后为-cnt-1,cnt为循环次数。取反相当于1111 1111 – cx = -1 + cnt + 1 = cnt
not ax ;含结束符长度
dec ax ;去掉结束符
ret
strlen endp
通过变量传递参数
主程序与子程序公用一组变量,实现参数的传递。不同模块间共享时,需要声明。这种结构独立性差
通过堆栈传递参数
- 主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数
- 子程序将出口参数压入堆栈,主程序弹出堆栈取得它们
要注意堆栈的分配情况,保证参数存取正确、子程序正确返回,并保持堆栈平衡
- 主程序实现平衡堆栈:
add sp,n
- 子程序实现平衡堆栈:
ret n
例:求字数组array中所有元素的累加和(设结果无溢出),结果存于sum
中
;主程序
.model tiny
.code
.startup
mov ax,offset array ;数组首地址
push ax
mov ax , count ;数组长度
push ax
mov ax,offset sum ;sum地址
push ax
call sumc ;调用子程序
.exit 0
array dw 100 dup(20)
count equ ($-array)/2
sum dw ?
end
;子程序
sumc proc
push bp
mov bp,sp ;利用BP间接寻址存取参数,SP为堆栈的栈顶指针
push ax
push cx
push si
push di
mov si,[bp+8] ;SS:[BP+8]指向array偏移地址
mov cx,[bp+6] ;SS:[BP+6]指向元素个数
mov di,[bp+4] ;SS:[BP+4]指向sum偏移地址
xor ax,ax
sumc1: add ax,[si]
add si,2
loop sumc1
mov [di],ax ;存放结果
pop di
pop si
pop cx
pop ax
pop bp
ret 6 ;表示将堆栈指针加6,之前主程序调用时入栈了3个字,现在加6使堆栈恢复原来的状态
sumc endp
end
嵌套子程序
;实现AL内容的显示
ALdisp proc
push ax
push cx
push ax ;暂存ax
mov cl,4
shr al,cl ;转换、显示al的高4位
call htoasc ;转换子程序调用(嵌套)
call dischar ;显示子程序调用(嵌套)
pop ax ;转换、显示al的低4位
call htoasc ;转换子程序调用(嵌套)
call dischar ;显示子程序调用(嵌套)
pop cx
pop ax
ret
ALdisp endp
;实现AX内容的显示
AXdisp proc
push ax ;保护ax
push ax ;暂存ax
mov al,ah
call ALdisp ;显示ah,子程序调用(嵌套)
pop ax ;恢复AX
call ALdisp ;显示al,子程序调用(嵌套)
pop ax ;恢复AX
ret
AXdisp endp
;将AL低4位表达的一位16进制数转换为ASCII码
htoasc proc
and al,0fh
cmp al,9
jbe htoasc1
add al,37h
ret
htoasc1: add al,30h
ret
htoasc endp
;将AL中的ASCII码表示的字符在屏幕上显示输出
dischar proc
push ax
push dx
mov dl,al ;显示
mov ah,2
int 21h
pop dx
pop ax
ret ;子程序返回
dischar endp
综合例题
显示有符号十进制数
- 子程序在屏幕上显示一个有符号十进制数
- 子程序还包含将二进制数转换为ASCII码的过程
- 显示时,负数用“-”引导,正数直接输出、没有前导字符
- 子程序的入口参数用共享变量传递,主程序调用该子程序显示10个数据
程序处理过程:
- 首先判断数据是零、正数或负数,是零显示“0”退出
- 是负数,显示“-”,求数据的绝对值;
- 接着数据除以10,余数加30H转换为ASCII码压入堆栈
- 重复上一步,直到余数为0结束
- 依次从堆栈弹出各位数字,进行显示
本例采用16位寄存器表达数据,所以只能显示+327677~-32768间的数值
.code
.startup
mov cx,count
mov bx,offset array
again: mov ax,[bx]
mov wtemp,ax ;将入口参数存入共享变量
call write ;调用子程序显示一个数据
inc bx
inc bx
call dpcrlf ;便于显示下一个数据
loop again
.exit 0
count = 10
array dw 1234,-1234,0,1,-1,32767
dw -32768,5678,-5678,9000
wtemp dw ? ;共享变量
end
;显示有符号10进制数的通用子程序
;入口参数:共享变量wtemp
write proc
push ax
push bx
push dx
mov ax,wtemp ;取出显示数据
test ax,ax ;判断零、正数或负数
jnz write1
mov dl,'0' ;是零,显示“0”后退出
mov ah,2
int 21h
jmp write5
write1: jns write2 ;是负数,显示“-”
mov bx,ax ;AX数据暂存于BX
mov dl,'-'
mov ah,2
int 21h
mov ax,bx
neg ax ;数据求补(求绝对值)
write2: mov bx,10 ;10压入堆栈,作为退出标志
push bx
write3: cmp ax,0 ;数据(余数)为零
jz write4 ;转向显示
sub dx,dx ;扩展被除数DX.AX
div bx ;数据除以10:DX.AX÷10
add dl,30h ;余数(0~9)转换为ASCII码
push dx ;数据各位先低位后高位压入堆栈
jmp write3
write4: pop dx ;数据各位先高位后低位弹出堆栈
cmp dl,10 ;是结束标志10,则退出
je write5
mov ah,2 ;进行显示
int 21h
jmp write4
write5: pop dx
pop bx
pop ax
ret ;子程序返回
write endp
end
计算有符号数平均值
- 子程序将16位有符号二进制数求和,然后除以数据个数得到平均值
- 子程序的入口参数利用堆栈传递,主程序需要压入数据个数和数据缓冲区的偏移地址。子程序通过
BP
寄存器从堆栈段相应位置取出参数 - 子程序的出口参数用寄存器
AX
传递 - 主程序提供10个数据,并保存平均值
.code
.startup
mov ax,count
push ax ;压入数据个数
mov ax,offset array
push ax ;压入缓冲区偏移地址
call mean ;调用子程序求平均值
add sp,4 ;平衡堆栈
mov wmed,ax ;保存平均值(不含余数)
.exit 0
count = 10
array dw 1234,-1234,0,1,-1,32767
dw -32768,5678,-5678,9000
wmed dw ? ;存放平均值
;计算16位有符号数平均值子程序
;入口参数:顺序压入数据个数和缓冲区偏移地址
;出口参数:AX=平均值
mean proc
push bp
mov bp,sp
push bx ;保护寄存器
push cx
push dx
push si
push di
mov bx,[bp+4] ;从堆栈取出偏移地址
mov cx,[bp+6] ;从堆栈取数据个数
xor si,si ;SI保存求和的低16位值 用32位来存储求和结果,防止溢出
mov di,si ;DI保存求和的高16位值
mean1: mov ax,[bx] ;取出一个数据→AX
cwd ;AX符号扩展→DX
add si,ax ;求和低16位
adc di,dx ;求和高16位 注意是用adc指令
inc bx ;指向下一个数据
inc bx
loop mean1 ;循环
mov ax,si
mov dx,di ;累加和在DX.AX
mov cx,[bp+6] ;数据个数在CX
idiv cx ;有符号数除法,求的平均值在AX中、余数在DX中
pop di ;恢复寄存器
pop si
pop dx
pop cx
pop bx
pop bp
ret
mean endp
end