AHU汇编语言

AHU汇编语言

写在前面:

对AHU汇编课的一些笔记总结,我自认为总结的还算全面,但受限于个人水平,有些地方可能会出现错误,如果有什么遗漏或者错误之处,可以发邮件到:howiewang.cs@gmail.com

Ch1-汇编语言基础知识

进制转换

二进制/十六进制数→十进制数:

$N=101101.1B=12^5+12^3+12^2+12^0+1*2^{-1}=45.5D$

$N=5F H=516^1+1516^0=95D$

十进制数 → 二进制数:

13/2=6 …….1

6/2=3……….0

3/2=1……….1

1/2=0……….1

$N=13D=1101B$

十进制数→十六进制数:

先转二进制,然后四个一组,整数部分高位补0凑4位,小数部分低位补0凑4位。

进制计算

43A5+5A34=9DD9

2A34*0025=61984(H) 列竖式自己算一下即可

补码

负数取反加1,正数不变即可。

例如用8位二进制表示-3的补码:

$[-3]_补$:0000 0011取反加1,即1111 1101

如果此时要用16位二进制表示-3的补码,我们只需要在前面加上8个1即可,这叫做符号扩展,对于负数来说,符号扩展是在前面补1,正数是在前面补0。

补码的运算:

$[X+Y]补=[X]补+[Y]_补$

$[X-Y]补=[X]补+[-Y]_补$

如果是在机器中限制了二进制位数且加完后超过了二进制位数则会舍弃高位。

例如8位加法运算:0001 1001+1110 0000=(1,这个1舍弃) 0000 0111

字符表示和逻辑运算

ASCII: 30-39:’0’ - ‘9’ 41-5A:’A’ - ‘Z’ 61-7A: ‘a’ - ‘z’

回车:0dh

换行:0ah

空格:20h

逻辑运算: 与,或,非,异或

Ch2-计算机基本原理

存储器

16位结构的CPU:

8086是十六位结构的CPU,16位结构的CPU具有以下四个方面的结构特征:

1.数据总线为16位

2.运算器一次最多可以处理16位的数据

3.寄存器最大宽度16位

4.寄存器与运算器之间的通路16位

8086中,8个bit(二进制位)一个byte(字节),2个byte(字节)一个word(字)。

高位字节/低位字节:

UHNtGF.png

上图中的字和字节在内存中表示一定要明白:

(31200H)处的字包含两个字节A28FH,分别在31201H,31200H,因为字的前半部分是高位字节在高位,但是字的位置是由低位字节表示的。

8086CPU有20位地址总线,可以传送20位地址,也就是说物理地址20位。20位地址可以标定的内存单元有1M,即寻址能力可达1MB。

存储器分段:

UHRFqf.png

物理地址=段地址*16+偏移地址​

每个段大小位64KB(偏移地址从0000-FFFF)。

段的类型:

代码段—用于存放指令,代码段段基址存放在段寄存器CS

数据段—用于存放数据,数据段段基址段地址存放在段寄存器DS

附加段—用于辅助存放数据,附加段段基址存放在段寄存器ES

堆栈段—是重要的数据结构,可用来保存数据、地址和系统参数,堆栈段段基址存放在段寄存器SS

写程序时,代码段必须要有。

逻辑地址:

逻辑地址是用户编程时使用的地址,分为段地址偏移地址两个部分。

UjzrVI.png

逻辑地址:如上如中存储单元C8的逻辑地址为: 1123H:0013H 或者 1124H:0003H

公式:段地址*16+偏移地址=物理地址 ( 相当于短地址在十六进制下左移一位再加上偏移地址即为物理地址)

例题 段基址为1896H,偏移地址为1655H。其物理地址为多少?

18960H+1655H=19FB5H

UvpVpT.png

通用寄存器:

8086 CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,有时候也可以存放地址,被称为通用数据寄存器

①AX:累加器,运算时较多使用这个寄存器,有些指令规定必须使用它。

②BX:基址寄存器,除了存放数据,它经常用来存放一段内存的起始偏移地址。

③CX:计数寄存器,除了存放数据,它经常用来存放重复操作的次数。

④DX:数据寄存器,除了存放数据,它有时存放32位数据的高16位。

一个字存放在16位的寄存器中,这个字分为高位字节和低位字节,高位字节存储在寄存器的高8位(AH),第八位(AL)同理。

地址寄存器:

16位的8086处理器有4个16位的通用地址寄存器。它们的主要作用是存放数据的所在偏移地址,也可以存放数据。这4个寄存器不能再拆分使用。

①SP:堆栈指针,这是一个专用的寄存器,存放堆栈栈顶的偏移地址。

②BP:基址指针,可以用来存放内存中数据的偏移地址。

③SI:源变址寄存器,它经常用来存放内存中源数据区的偏移地址,所谓变址寄存器,是指在某些指令作用下它可以自动地递增或递减其中的值。

④DI:目的变址寄存器,它经常用来存放内存中目的数据区的偏移地址,并在某些指令作用下可以自动地递增或递减其中的值。

段寄存器:

16位80x86处理器有4个16位的段寄存器,分别命名为CS,SS,DS,ES。它们用来存放4个段的段基址。

①CS:代码段寄存器,用来存放当前正在执行的程序段的段基址。

②SS:堆栈段寄存器,用来存放堆栈段的段基址。

③DS:数据段寄存器,用来存放数据段段基址。

④ES:附加段寄存器,用来存放另一个数据段的段基址。

指令指针寄存器:

IP:指令指针寄存器,存放即将执行指令的偏移地址。

指令指针寄存器:

FLAGS:存放CPU的两类标志。

状态标志:反映处理器当前的状态,如有无溢出,有无进位等。

状态标志有6个:CF、PF、AF、ZF、SF和OF

控制标志:用来控制处理器的工作方式,如是否响应可屏蔽中断等

控制标志有3个:TF、IF和DF

堆栈区:

当堆栈区为空时,栈顶和栈底是重合的。数据在堆栈区存放时,必须以字存入,每次存入一个字,后存入的数据依次放入栈的低地址单元中。栈指针SP每次减2,由栈指针SP指出当前栈顶的位置,数据存取时采用后进先出的方式

Ch3-汇编程序实例/上机操作

DOS命令

1.盘

E: 回车即可进入E盘

2.CD 选择目录

E:\>CD MASM 进到MASM的子目录

E:\>MASM>CD MY 进到MY的子目录

E:\>MASM>MY>CD.. 退到上一级目录MASM

E:\>MASM>CD\ 退回到根目录

3.DIR 显示目录和文件

E:\>MASM>DIR

E:\>MASM>DIR .ASM 列出扩展名为ASM的文件,\为通配符

E:\>MASM>DIR HELLOW. 列出名为ASM的文件,\为通配符

E:\>MASM>DIR HE*.??? 列出名为HE开头,扩展名有三个字符的文件

4.REN 改变文件名

E:\>REN H1.TXT H2.ASM 把 H1.TXT 改为 H2.ASM

5.CLS 清屏

6.DEL 删除文件

E:\>DEL C.TXT

7.MD 建立目录

E:\>MD MASM

8.RD 删除目录

E:\>RD MASM

9.COPY 复制文件

E:\>COPY H1.TXT H2.TXT 复制文件H1.TXT到文件H2.TXT

10.TYPE 显示文本文件内容

E:\>TYPE C.TXT

11.HELP 显示命令格式的用法

E:\>HELP 显示所用命令的格式

E:\>HELP DIR 显示DIR命令的用法

汇编程序实例

输出输入字符的下一个字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
code segment 
assume cs:code
start:
mov ah,1 ;输入
int 21h
mov dl,al ;al传到dl
add dl,1 ;dl+1
mov ah,2 ;显示
int 21h
mov ah,4ch
int 21h
code ends
end start

显示字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data segment 
string db 'hello,world!$'
data ends

code segment
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
mov ah,4ch
int 21h
code ends
end start

1.编辑 EDIT HELLO.ASM

2.汇编 MASM HELLO.ASM

3.连接 LINK HELLO.OBJ

4.运行 HELLO.EXE

DEBUG

(1) debug的R命令查看,改变寄存器的内容。
查看:
改变寄存器的内容:将AX=0000改为AX=1234

(2) 用debug的D命令查看内存中的内容
命令: -d 短地址:偏移地址
-d 短地址:偏移地址x 偏移地址y 可以查询短地址:偏移地址x 到 短地址:偏移地址y 的内存中内容

(3) 用debug的E命令改写内存中的内容
命令: -e 起始地址 数据 数据 数据…..
命令:-e 起始地址
对从起始地址开始的内存进行改写,按空格改写下一个,回车改写结束。
向内存中写入字符串:

(4) 反汇编命令U

(5) 运行程序命令G

(6) 跟踪程序命令T

(7) 单步执行程序命令P

(8) 退出命令Q

DOS系统功能调用(INT 21H)

1.键盘输入并回显(1号功能)

1
2
MOV AH,1
INT 21H

等待从键盘输入一个字符,将该字符的ASCII码送入AL中,并送屏幕显示。

2.显示单个字符(2号功能)

1
2
3
MOV AH,2
MOV DL,'A'
INT 21H

显示DL的字符,执行后AL寄存器的值被修改为DL的值

3.显示字符串(9号功能)

1
2
3
MOV AH,9
LEA DX,STR
INT 21H

DS:DX指向以$结束的字符串STR,执行后AL寄存器的值被修改为$

4.键盘输入到缓冲区(0AH/10号功能)

1
2
3
MOV AH,0AH
LEA DX,BUF
INT 21H
1
2
3
4
5
6
7
8
9
10
11
12
   data segment
buffer db 81 ;定义缓冲区大小为9个字节
db ? ;大小未定
db 81 dup(?) ;dup代表保存空间不被占用
...
data ends

mov dx, seg buffer ;伪指令seg取得buffer的段地址
mov ds, dx ;设置数据段DS
mov dx, offset buffer
mov ah, 0ah
int 21h

5.结束程序返回DOS(4CH号功能)

1
2
MOV AH,4CH
INT 21H

Ch4-操作数的寻址方式

1.立即寻址方式

操作数就在指令中,紧跟在操作码之后,操作数作为指令的一部分存放在代码段

1
2
3
MOV AL,6H

MOV AX,12AFH

TIPS:

①执行时无需去内存取数,因此称为立即数。

②主要用于寄存器赋初值。

③立即数只能作为源操作数,并且长度与目的操作数一致。 不可出现MOV AL,12AFH

2.寄存器寻址方式

操作数就是寄存器中的值。指令中给出寄存器的名字

1
2
3
4
MOV AX,BX  (√)
MOV AL,BL (√)
MOV AL,BX (x)
MOV AX,BL (x)

3.直接寻址方式

操作数的有效地址EA就在指令中,机器默认段地址在DS中。

aCfgvn.png

EA和段寄存器中的段地址组成操作数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;存储器读操作
MOV AX,DS:[2000H]

;存储器写操作
MOV DS:[2000H],AX

;符号地址:直接寻址方式除了用数值作为有效地址之外,还可以用符号地址的形式。为存储单元定义一个名字,该名字就是符号地址。
VALUE DW 5678H
MOV AX,VALUE
MOV AX,[VALUE]

;段前缀:在与内存有关的寻址方式中,操作数的段地址默认为数据段,80X86规定除了数据段之外,数据还可以存放在其他三种段中。如果操作数在其他段中存放,称为段超越,需要在指令中用段超越前缀指出,即用操作数前加上段寄存器名和冒号表示。
VALUE EQU 1000H
MOV AX,DS:[VALUE]
MOV AX,ES:[VALUE]

4.寄存器间接寻址方式

特点:操作数的有效地址在寄存器中,只允许使用BX、BP、SI和DI寄存器

​ 物理地址=10H × (DS) + (BX)

​ 物理地址=10H × (DS) + (SI )

​ 物理地址=10H × (DS) + (DI )

​ 物理地址=10H × (SS) + (BP)

aC5H2Q.png

1
2
3
MOV AX,[BX]
MOV DX,[BP]
MOV ES:[DI],AX

5.寄存器相对寻址方式

特点:操作数的有效地址是一个寄存器和位移量之和。

​ 物理地址=10H × (DS) + (BX) + 8(16)位位移量

​ 物理地址=10H × (DS) + (SI ) + 8(16)位位移量

​ 物理地址=10H × (DS) + (DI ) + 8(16)位位移量

​ 物理地址=10H × (SS) + (BP) + 8(16)位位移量

1
2
3
4
5
6
MOV AX,TOP[SI]
;TOP为符号地址,即位移量。
;已知(DS)=1500H ,(SI)=7310H, TOP=25H
;有效地址EA=TOP+SI=7310H+25H=7335H
;物理地址=DS*10H+EA=1C335H
;若(1C335H)=2428H ,(AX)=2428H
1
2
MOV [BX+2623H],AX
;此时的位移量为2623H
1
2
3
4
5
6
MOV  AX,ARRY[BX]       
MOV AX,[ARRY][BX]
MOV AX,[ARRY+BX]
MOV AL,BUF[BX]
MOV AL,[BX+8H]
MOV AL,[BX].8H

6.基址变址寻址方式

操作数的有效地址是一个基址寄存器和一个变址寄存器的内容之和

基址寄存器BX和BP,变址寄存器SI和DI

1
2
3
4
5
MOV AX, [BX+DI]
;执行前:已知(DS)=2100H,(BX)=0158H,(DI)=10A5H,(221FD)=34H,(221FE)=95H,(AX)=0FFFFH。则
;有效地址EA=(BX)+(DI)=0158H+10A5H=11FDH
;物理地址=(DS)*10H+EA=21000H+11FDH=221FDH
;执行后,(AX)=9534H
1
2
3
4
5
6
MOV  AX,[BX][SI]         ;默认DS寄存器作段地址
MOV AX,[BP][DI] ;默认SS寄存器作段地址
MOV AX,ES:[BX][DI] ;指定ES寄存器作段地址
MOV DX,[BP][SI] ;默认SS寄存器作段地址
MOV [BX+DI], CX ;默认DS寄存器作段地址
MOV [BP+SI], AL ;默认SS寄存器作段地址

7.相对基址变址寻址方式

操作数的有效地址是一个基址寄存器和一个变址寄存器以及一个位移量之和。
基址寄存器BX和BP,变址寄存器SI和DI。
默认段寄存器搭配和寄存器间接寻址方式一样

aCoQYT.png

1
2
3
4
MOV  AX,MASK[BX][SI]      ;默认DS寄存器作段地址
MOV AX,[MASK+BX+SI] ;默认DS寄存器作段地址
MOV AX,[BX+SI].MASK ;默认DS寄存器作段地址
;以上三种表示形式实现的功能是一样的。其有效地址EA=MASK+(BX)+(SI);物理地址=(DS)*10H+EA。

Ch5-常用指令系统

数据传送指令

①MOV:

最基本的指令,注意源操作数和目的操作数的长度要一致

目的操作数不能是CS和IP,因为CS:IP是程序当前的地址

目的操作数不可是立即数,如:MOV 9H,AL

源操作数不能超过8位二进制,例如:MOV AH,258 (错误)

MOV [BX],0 是错误的,应该写为MOV BYTE PTR[BX],0 或者 MOV WORD PTR[BX],0

段地址寄存器须通过寄存器得到段地址,不能直接由符号地址、段寄存器、立即数得到。

1
2
3
4
MOV  DS, DATA_SEG     ;段寄存器不接受符号地址
MOV DS, ES ;段寄存器之间不能直接传送
MOV DS, 1234 ;段寄存器不接受立即数
MOV CS, AX ;指令合法,但代码段寄存器不能赋值

两个操作数不能同为内存单元,例如MOV [BX],BUFF MOV [AX],[BX]

MOV AX,OFFSET TABLE(正确) MOV AX,OFFSET TABLE(错误), OFFSET出来的有效地址总是16位

②PUSH 进栈指令

格式: PUSH SRC
操作: (SP)←(SP)-2
(( SP)+1,(SP))←(SRC)
堆栈:后进先出内存区,以字为单位传送,SS:SP总是指向栈顶。

③POP 出栈指令

格式: POP DST
操作: (DST)←((SP)+1,(SP))
(SP)←(SP)+2

1
2
3
MOV BX,1234H
PUSH BX ;将BX的值压入栈
POP AX ;将栈弹出并赋值给AX

④XCHG 交换指令

格式: XCHG OPR1, OPR2
操作: (OPR1)<==>(OPR2)
功能:把两个操作数互换位置。
遵循双操作数指令的规定,但操作数不能为立即数。

1
2
3
4
5
6
7
XCHG  AX,  BX      ;两个寄存器长度相等
XCHG AX, [BX] ;AX要求[BX]也取字单元
XCHG AX, VAR ;VAR 必须是字变量
以下指令是错误的:
XCHG AX, 5 ;显然操作数不能为立即数
XCHG [BX], VAR ;操作数不能同为内存单元
XCHG AX, BH ;操作数长度要一致

累加器专用传送指令

① IN 输入指令

长格式:IN AL,PORT(字节) ;00~FFH
IN AX,PORT(字)

操作:AL←(PORT)
AX←(PORT)

功能:把端口PORT的数据输入到累加器。

短格式:IN AL,DX(字节) ; PORT放入DX
IN AX,DX(字)

操作:AL←((DX))
AX← ((DX))

功能:把DX指向的端口的数据输入到累加器。

1
2
3
4
MOV DX,2F8H
IN AL,DX
;把端口2F8H的8为数据输入到累加器AL中
IN AX,[DX] 错误 端口号不可以用[]

②OUT 输出指令

长格式:OUT PORT,AL (字节) ;00-FFH
OUT PORT,AX (字)

操作:PORT ← AL
PORT ← AX

功能:把累加器的数据输出到端口PORT。

短格式:OUT DX,AL (字节) ;0000-FFFFH
OUT DX,AX (字)

操作: (DX) ← AL
(DX) ← AX

功能:把累加器的数据输出到DX指向的端口。

1
2
OUT 61H,AL
OUT DX,AL

③XLAT换码指令

格式:XLAT
操作:AL←(BX+AL)
功能:把BX+AL的值作为有效地址,取出其中的一个字节送AL。

地址传送指令

①LEA 有效地址送寄存器指令

格式:LEA REG,SRC
操作:REG←SRC
功能:把源操作数的有效地址EA送到指定的寄存器。

1
2
3
LEA   BX,TABLE
MOV BX,OFFSET TABLE
;上面2条指令等效。TABLE无论是何类型的变量,其有效地址总是16位。
1
2
3
LEA   BX, [2016H]
MOV BX, OFFSET [2016H]
指令执行后,BX=2016H。

②LDS 指针送寄存器和DS指令

格式: LDS REG, SRC
操作:REG←(SRC)
DS←(SRC+2)
功能:把源操作数SRC所指向的内存单元中的两个字送到指定的寄存器REG和DS。

1
2
3
LDS  SI,[BX]
;指令执行前,如DS=2000H,BX=0400H,(2000:0400)=1234H, (2000:0402)=5678H,
;指令执行后,SI=1234H,DS=5678H。

③LES 指针送寄存器和ES指令

格式: LES REG, SRC
操作:REG←(SRC)
ES←(SRC+2)
功能:把源操作数SRC所指向的内存单元中的两个字送到指定的寄存器REG和ES。

1
2
3
LES   DI,  [10H] 
;DS=C000H, (C0010H)=0180H, (C0012H)=2000H
;结果 DI=0180H, ES=2000H

标志寄存器传送指令

LAHF 标志寄存器FLAGS的低字节送AH
SAHF AH送FLAGS的低字节
PUSHF 标志进栈
POPF 标志出栈
以上传送类指令均不影响标志位,除SAHF, POPF 外.

1
2
3
4
LAHF          ;标志寄存器低字节送AH寄存器
SAHF ;AH送标志寄存器
PUSHF ;标志入栈
POPF ;标志出栈

算术运算指令

①CBW 扩展指令

AL扩展为AX ,字节扩展为字

扩展为符号扩展

②CWD 扩展指令

AX扩展为DX,AX 字扩展为双字

扩展为符号扩展

负数补1,正数补0

1
2
3
MOV  AL,52H  ;AL中的52H是正数
CBW ;指令执行后,AX=0052H
CWD ;指令执行后,DX=0000H,AX=0052H
1
2
3
4
;负数的扩展
MOV AL,88H ;AL中的88H是负数
CBW ;执行后,AX=FF88H
CWD ;执行后,DX=FFFFH AX=FF88H

③ADD 加法

格式:ADD DST,SRC
操作:(DST)← (DST) + (SRC)

加法指令执行后会影响标志寄存器中的CF和OF标志位

无符号数的溢出标志位CF(Carry Flag)

1
2
3
MOV AL,72H
ADD AL,93H
;执行后 AL=105H,但是AL只可以存放一个字节,因此CF=1

有符号数的溢出标志位OF(overflow flag)

1
2
3
MOV AL,92H
ADD AL,93H
;-110+(-109)=-219 超过了八位,所以会溢出,OF=1

④ADC 带进位加法指令

格式:ADC DST,SRC
操作:(DST)←(DST)+(SRC)+ CF

1
2
3
4
5
6
7
DX=2000H,AX=8000H
BX=4000H,CX=9000H

ADD AX,CX
ADC DX,BX
;第一次执行ADD AX,CX时,因为无符号溢出导致CF=1
;第二次执行ADC DX,BX时,因为 DX+BX=6000H,同时由于上次计算的影响CF=1,因此 DX最终等于6001H

⑤INC 自增加一

格式:INC OPR
操作:(OPR)←(OPR)+1

条件标志位(条件码) 最主要有:
进位CF, 零ZF, 符号SF, 溢出OF

⑥SUB 减法指令

格式:SUB DST,SRC
操作:(DST)← (DST) -(SRC)

1
2
3
4
5
MOV AL,72H
SUB AL,93H

;72H-93H 在看作无符号情况下,出现溢出,即CF=1
;再看做有符号情况下,相当于0111 0010B=+72H 1001 0011B=-13H 相减后不出现溢出,因此OF=0

⑦SBB 带借位减法指令

格式:SBB DST,SRC
操作:(DST)← (DST) -(SRC)- CF

1
2
3
4
5
6
7
8
DX=2001H, AX=8000H
BX=2000H, CX=9000H

SUB AX,CX ;低位字减法
SBB DX,BX ;高位字减法

;第一条指令执行后,AX=F000H,CF=1,而对OF=0,ZF=0,SF=1,不必在意。
;第二条指令执行后,DX=0000H,CF=0,OF=0,表示结果正确。ZF=1,SF=0。

⑧DEC 自减1指令

格式:DEC OPR
操作:(OPR)←(OPR)- 1

⑨NEG 求补指令

格式:NEG OPR
操作:(OPR)← -(OPR)
功能:对OPR求补,求- OPR, 即反码+1.
只有OPR为0时,CF=0。

1
2
3
4
5
6
MOV   AX,3
NEG AX
;3的补码 AX=FFFD H
MOV DX,0
NEG DX
0的补码 DX=0

⑩CMP比较指令

格式:CMP OPR1, OPR2
操作:(OPR1)-(OPR2)
不回送结果,只产生标志位。

1
2
3
4
5
6
MOV   AX,5
DEC AX
CMP AX,5

;指令序列执行后,AX=4,ZF=0,SF=1,CF=1,OF=0。
;CMP指令虽作减法,但不回送结果,只是产生标志位,为程序员比较两个数的大小提供判断依据。

乘法/除法指令

①MUL 无符号数乘法

格式: MUL SRC
操作:

要提前设置好AX

操作数为字节时: (AX)← (AL) × (SRC)

操作数为字时: (DX,AX)← (AX) × (SRC)

②IMUL 有符号数乘法

格式: IMUL SRC
操作:
操作数为字节时: (AX)← (AL) X (SRC)
操作数为字时: (DX,AX)← (AX) X (SRC)

两个相乘的数必须长度相同。
SRC不能是立即数

1
2
3
4
5
6
7
8
9
MOV   AL,0F1H
MOV BL,AL
MUL BL
指令序列执行后,AX=E2E1H。
如果看成是两个带符号相乘,则应选择如下指令:
MOV AL,0F1H
MOV BL,AL
IMUL BL
指令序列执行后,AX=00E1H。说明了两个负数相乘,结果为正数。

③DIV 无符号数触发

格式:DIV SRC

操作取决于SRC的大小

字节操作: AL<= AX / SRC 的商

​ AH<=AX / SRC 的余数

字操作: AX<= (DX,AX) / SRC

​ DX<=(DX,AX) / SRC 的余数

1
2
3
4
5
6
7
8
9
10
MOV AX,300H
MOV BL,2
DIV BL
;300H / 2 = 3*2^8 / 2= 768 /2 = 384 产生了溢出 AL放不下

;AX扩展成双字即可
MOV AX,300H
CWD
MOV BX,2
DIV BX

④IDIV 带符号数除法指令

格式: IDIV SRC
操作与DIV 相同

余数和被除数同符号。
被除数长度应为除数长度的两倍。
SRC不能是立即数,因为要根据SRC的类型来确定操作类型。

算术运算综合题:

重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点

算术运算综合举例,计算:(V-(X×Y+Z-16))/X,其中X、Y、Z、V均为16位带符号数,在数据段定义,要求上式计算结果的商存入AX,余数存入DX寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
DATAS SEGMENT
x dw 4
y dw 2
z dw 14h
v dw 18h
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov ax,x
imul y
mov cx,ax
mov bx,dx
mov ax,z
cwd
add cx,ax
adc bx,dx
sub cx,16
sbb bx,0
mov ax,v
cwd
sub ax,cx
sbb dx,bx
mov ax,x
idiv x


MOV AH,4CH
INT 21H
CODES ENDS
END START

BCD码十进制调整指令

BCD码 (Binary Coded Decimal):用二进制编码表示十进制数.
四位二进制数表示一位十进制数,由于四位二进制数的权分别为8,4,2,1,所以又称为8421码.

①DAA 加法十进制调整指令

格式:DAA
操作:
IF CF=1 or AL高4位是[A~F] THEN AL+60H.
IF AF=1 or AL低4位是[A~F] THEN AL+6

1
2
3
4
;如AL=28H=28(BCD),BL=65H=65(BCD)
ADD AL,BL ;AL=28H+65H=8DH
DAA ;AL=AL+6H=8DH+6H=93H=93(BCD)
;AL和BL中都是用BCD码表示的十进制数,含义分别是28和65,ADD指令作二进制加法后得到8DH,不是BCD码,DAA指令作用后,把和调整为93H,但它表示的是十进制数93的BCD码。

②DAS 减法十进制调整指令

格式:DAS
操作:
IF AF=1 OR AL低4位是[A~F] THEN AL-6
IF CF=1 OR AL高4位是[A~F] THEN AL-60H.

道理和DAA 一样,计算完后加一个DAS进行调整即可

逻辑指令

①AND与指令

格式: AND DST,SRC
操作: (DST)←(DST)&(SRC)

②OR 或指令

格式: OR DST,SRC
操作: (DST)←(DST) | (SRC)

③ NOT 非指令

格式: NOT OPR

④ XOR 异或指令

格式: XOR DST,SRC
操作: (DST)←(DST) xor (SRC)

⑤TEST 测试指令

格式: TEST OPR1,OPR2
操作: (OPR1) ∧ (OPR2)
TEST执行AND操作, 但不保存结果,只根据其特征置标志位

1
2
3
4
5
6
7
8
9
10
11
12
;屏蔽AL寄存器的高四位,如AL=36H
AND AL,0FH ;指令执行的结果使AL=06H

;对AL寄存器的最低两位置1,如AL=36H。
OR AL,03H

;对AL寄存器的最低两位取反,如AL=36H。
XOR AL,03H

;测试AL寄存器中的数,如果是负数则转到标号NEXT去执行。如AL=86H。
TEST AL,80H
JS NEXT

位移指令

①SHL 逻辑左移

格式:SHL OPR, CNT
CNT可以是1或CL寄存器,如需移位的次数大于1,则可以在该移位指令前把移位次数先送到CL寄存器。

aFwg5n.png

②SAL 算数左移

aFwg5n.png

③SHR 逻辑右移

aFwhvT.md.png

④SAR 算术右移

aFwLP1.png

⑤ROL循环左移

aFwjxK.md.png

⑥ROR 循环右移

aF0kGt.md.png

TIPS:以上所说的移动一位,指的是二进制的一位,并非十六进制的一位

1
2
3
4
;对AX中内容实现半字交换,即交换AH和AL中的内容。
MOV CL,8
ROL AX,CL
;如指令执行前,AX=1234H,指令执行后,AX=3412H。

串操作指令

①MOVS 串传送

MOVS DST, SRC ;将源串SRC传送到目的串DST中
MOVSB ;以字节为单位传送
MOVSW ;以字为单位传送

字节操作:
(ES:DI)←(DS:SI), SI=SI±1, DI=DI±1
字操作:
(ES:DI)←(DS:SI), SI=SI±2, DI=DI±2

CLD 设置正向

STD 设置反向

实现整个串传送的准备工作:

SI=源串首地址(如反向传送则是末地址).
DI=目的串首地址(如反向传送则是末地址).
CX=串长度.
设置方向标志DF.

例题:

在数据段中有一个字符串MESS,其长度为19,要求把它们转送到附加段中名为BUFF的一个缓冲区中,并显示出BUFF字符串,编制程序如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DATAS SEGMENT
mess db 'yu bao zui shuai$'
DATAS ENDS

ext segment
buff db 19 dup(?)
ext ends

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
mov ax,ext
mov es,ax
lea si,mess
lea di,buff
mov cx, 17
cld
rep movsb
mov bx,es
mov ds,bx
lea dx,buff
mov ah,9
int 21h

MOV AH,4CH
INT 21H
CODES ENDS
END START

②CMPS 串比较

CMPS SRC,DST ;操作数寻址方式固定
CMPSB ;字节
CMPSW ;字

字节操作:
(ES:DI)-(DS:SI), SI=SI±1, DI=DI±1
字操作:
(ES:DI)-(DS:SI), SI=SI±2, DI=DI±2
指令不保存结果,只是根据结果设置标志位。

例题:

在数据段中有一个长度为19的字符串MESS1,还有一个长度为19的字符串MESS2,比较它们是否相等。若相等显示‘Y’,否则显示‘N’。编制程序如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DATAS SEGMENT
mess1 db 'yu bao zui shuai$'
mess2 db 'yu bao zui shuai$'
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
mov es,ax
lea si,mess1
lea di,mess2
mov cx,17
cld
repe cmpsb
jz yes
mov dx,'N'
jmp disp
yes:
mov dx,'Y'
disp:
mov ah,2
int 21h

MOV AH,4CH
INT 21H
CODES ENDS
END START

③SCAS 串扫描

SCAS DST ;操作数寻址方式固定
SCASB ;字节
SCASW ;字

字节操作:
AL-(ES:DI), DI=DI±1
字操作:
AX-(ES:DI), DI=DI±2

指令不保存结果,只是根据结果设置标志位。

例题:

在附加段中有一个字符串MESS,其长度为19,要求查找其中有无空格符,若有空格符,把首次发现的空格符改为‘#’,存回该单元,并显示‘Y’,否则显示‘N’。编制程序如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
DATAS SEGMENT
mess1 db 'yu bao zui shuai $'
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
mov es,ax
mov cx,19
lea di,mess1
mov al,' '
cld
repne scasb
jz yes
mov dl,'N'
jmp disp
yes:
mov dl,'Y'
dec di
mov byte ptr es:[di],'#'
disp:
mov ah,2
int 21h
lea dx,mess1
mov ah,9
int 21h

MOV AH,4CH
INT 21H
CODES ENDS
END START

④STOS 存入串

STOS DST ;操作数寻址方式固定
STOSB ;字节
STOSW ;字

字节操作:
(ES:DI)←AL, DI=DI±1
字操作:
(ES:DI)←AX, DI=DI±2

例题:

写出把附加段EXT中的首地址为MESS,长度为9个字的缓冲区置为0值的程序片段。

1
2
3
4
5
6
7
MOV AX,MESS
MOV ES,AX
LEA DI,MESS
MOV CX,9
MOV AX,'0'
CLD
REP STOSW

⑤LODS 从串取

LODS SRC ;操作数寻址方式固定
LODSB ;字节
LODSW ;字

字节操作:
AL←(DS:SI), SI=SI±1
字操作:
AX←(DS:SI), SI=SI±2
指令一般不和REP连用。

⑥REP/REPE/REPZ/REPNE/REPNZ 重复

REP 重复
REPE / REPZ 相等或为零则重复
REPNE / REPNZ 不相等或不为零则重复

REP的作用
重复执行串操作指令,直到CX=0为止.,串操作指令每执行一次,使CX自动减1.

REPE/REPZ的作用

当CX ≠ 0 并且 ZF=1时,重复执行串操作指令,直到CX=0 或者 ZF=0为止。串操作指令每执行一次,使CX自动减1.

REPNE/REPNZ的作用
当CX ≠ 0 并且 ZF=0时,重复执行串操作指令,直到CX=0 或者 ZF=1为止。串操作指令每执行一次,使CX自动减1.

程序转移指令

①JMP 无条件转移指令

转移的目标地址和本跳跳转指令在同一个代码段,则为段内转移;否则是段间转移

转移的目标地址在跳转指令中直接给出,则为直接转移;否则是间接转移

1.段内直接转移

格式: JMP NEAR PTR OPR
操作: IP←IP+16位位移量
NEAR PTR为目标地址OPR的属性说明,表明是一个近(段内)跳转,通常可以省略。
位移量是带符号数,IP的值可能减小(程序向后跳),也可能增加(程序向前跳)

就是平时最经常写的那一种,例如 JMP DISP

2.段内间接转移

格式: JMP WORD PTR OPR
操作: IP←(EA)
可以使用除立即数以外的任何一种寻址方式。

1
2
3
4
5
6
7
8
9
;如果BX=2000H,DS=4000H,(42000H)=6050H,(44000H)=8090H,TABLE的偏移地址为2000H,分析下面四条指令单独执行后IP的值。
JMP BX ;寄存器寻址,IP=BX
JMP WORD PTR [BX] ;寄存器间接寻址,IP=[DS:BX]
JMP WORD PTR TABLE ;直接寻址,IP=[DS:TABLE]
JMP TABLE[BX] ;寄存器相对寻址,IP=[DS:(TABLE+BX)]
;第一条指令执行后,IP=BX=2000H。
;第二条指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H。
;第三条指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H。
;第四条指令执行后,IP=(DS:4000H)=(40000H+4000H)=(44000H)=8090H。

3.段间直接转移

格式: JMP FAR PTR OPR
操作: IP←OPR的偏移地址
CS←OPR所在段的段地址

4.段间间接转移

格式: JMP DWORD PTR OPR
操作: IP←(EA)
CS←(EA+2)
可以使用除立即数和寄存器方式以外的任何一种寻址方式。

1
2
3
如果BX=2000H,DS=4000H,(42000H)=6050H,(42002H)=1234H,指出下面指令执行后IP和CS的值。
JMP DWORD PTR [BX]
指令执行后,IP=(DS:2000H)=(40000H+2000H)=(42000H)=6050H;CS=(42002H)=1234H。

②条件转移指令

条件转移指令根据上一条指令所设置的标志位来判别测试条件,从而决定程序转向。
通常在使用条件转移指令之前,应有一条能产生标志位的前导指令,如CMP指令。
汇编指令格式中,转向地址由标号表示。
所有的条件转移指令都不影响标志位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
JZ  (JE)    
结果为零转移
格式: JZ OPR
测试条件:ZF=1

JNZ (JNE)
结果不为零转移
格式: JNZ OPR
测试条件:ZF=0

JS
结果为负转移
格式: JS OPR
测试条件:SF=1

JNS OPR
结果不为负(为正)转移
测试条件:SF=0

JO OPR
结果溢出转移
测试条件:OF=1

JNO OPR
结果不溢出转移
测试条件:OF=0

JP (JPE)
奇偶位为1转移
格式: JP OPR
测试条件:PF=1

JNP (JPO)
奇偶位为0转移
格式: JNP OPR
测试条件:PF=0

JB (JNAE,JC)
低于,(不高于等于,进位位为1),则转移.
格式: JB OPR
测试条件:CF=1

JNB (JAE,JNC)
不低于,(高于等于,进位位为0),则转移.
格式: JNB OPR
测试条件:CF=0

JCXZ
测试CX寄存器的值为0则转移
格式: JCXZ OPR
测试条件:CX=0


;比较两个无符号数,根据结果转移
JB (JNAE,JC)
低于,(不高于或等于,进位位为1),则转移.
格式: JB OPR
测试条件:CF=1

JNB (JAE,JNC)
不低于,(高于等于,进位位为0),则转移.
格式: JNB OPR
测试条件:CF=0

JBE (JNA)
低于或等于,(不高于),则转移.
格式: JBE OPR
测试条件:CF OR ZF=1

JNBE (JA)
不低于或等于,(高于),则转移.
格式: JNBE OPR
测试条件:CF OR ZF=0


;比较两个带符号数,根据结果转移
JL (JNGE)
小于,(不大于等于),则转移. <
格式: JL OPR
测试条件:SF XOR OF=1

JNL (JGE)
不小于,(大于等于),则转移. >=
格式: JNL OPR
测试条件:SF XOR OF=0

JLE (JNG)
小于等于,(不大于),则转移. <=
格式: JLE OPR
测试条件:(SF XOR OF) OR ZF=1

JNLE (JG)
不小于等于,(大于),则转移. >
格式: JNLE OPR
测试条件:(SF XOR OF) OR ZF=0

例题:

有一个长为19字节的字符串,首地址为MESS。查找其中的‘空格’(20H) 字符,如找到则继续执行,否则转标号NO。

1
2
3
4
5
6
7
8
MOV CX,19
MOV AL,20H
MOV DI,-1
LK:
INC DI
CMP AL,MESS[DI]
JCXZ NO
JNE LK

循环指令

LOOP 循环
LOOPZ / LOOPE 为零或相等时循患
LOOPNZ / LOOPNE 不为零或不相等时循环

指令:LOOP OPR
测试条件:CX ≠ 0,则循环

指令:LOOPZ / LOOPE OPR
测试条件:ZF=1 AND CX≠0 ,则循环

指令:LOOPNZ / LOOPNE OPR
测试条件:ZF=0 AND CX≠0 ,则循环

操作: 首先CX寄存器减1,然后根据测试条件决定是否转移。

1
2
3
4
5
6
7
8
;在首地址为MESS长为19字节的字符串中查找 ‘空格’(20H) 字符,如找到则继续执行,否则转标号NO。用循环指令实现程序的循环。
MOV AL,20H
MOV CX,19
MOV DI,-1
LK: INC DI
CMP AL, MESS[DI]
LOOPNE LK
JNZ NO

Ch6-伪指令和源程序格式

伪指令概述

指令是在程序运行期间由计算机的CPU来执行的。

伪指令是在汇编程序对源程序进行汇编期间由汇编程序处理的操作

段定义伪指令格式:

1
2
3
segment_name    SEGMENT
……
segment_name ENDS

ASSUME伪指令格式:

1
ASSUME register_name:segment_name,register_name:segment_name

数据定义与存储器单元分配伪指令

定义数据类型的伪指令:

DB:用来定义字节,其后的每个操作数都占用1个字节。
DW:用来定义字,其后的每个操作数都占用1个字。
DD:用来定义双字,其后的每个操作数都占用2个字。
DF:用来定义六个字节的字,其后的每个操作数都占用48位。
DQ:用来定义4个字,其后的每个操作数都占用4个字。
DT:用来定义10个字节,其后的每个操作数都占用10个字节。

DUP 复制伪指令
格式:count DUP (operand, …, operand)
操作:将括号中的操作数重复count次,count可以是一个表达式,其值应该是一个正数。

DUP操作可嵌套。

例子1:

1
2
3
D_BYTE    DB  10, 10H
D_WORD DW 14,100H,-5,0ABCDH
D_DWORD DD 4×8

数据在内存中的存放如下:

0A 10 0E 00 00 01 FB FF CD AB 20 00 00 00

解释:

0A: 10 10: 10H

0E 00(因为是字所以16位):即00 0E,即14,内存中反着装

00 01:即01 00,即100H,内存中是反着的

FB FF:即-5的补码

CD AB:即0ABCDH

20 00 00 00:即 00 00 00 20H=32

例子2:

1
2
3
MESSAGE DB ‘HELLO?’,?     ;问号?通常被系统置0
DB ‘AB’,?
DW ‘AB’ ;注意这里‘AB’作为串常量按字类型存放

数据在内存中的存放如下:

48 45 4C 4C 4F — 41 42 — 42 41

48 45 4C 4C 4F 3F: ‘HELLO?’

— : ? 预留的空间

41 42 — :’AB’ 和 ?预留的空间

42 41:‘AB’

这里需要解释为什么 ‘AB’ 在DB和DW没有什么区别呢?

因为’AB’在DW时依然是按照一个字符一个字符存入的,对于 DW ‘A’,’B’才是一个字符占用16位。

我们做个实验可以发现 DW ‘ba’,’c’,’e’ 中ba和c,e的存放是有区别的。

aA6xpQ.png

例子3:

用操作符复制操作数。

1
ARRAY   DB 2 DUP(1,3,2 DUP(4,5))

ARRAY:即为 1 3 4 5 4 5 1 3 4 5 4 5

在内存中存放即为: 01 03 04 05 04 05 01 03 04 05 04 05

TIPS:

1.数据在内存中负数以补码形式存储。

2.允许数据表达式,例如 D_DWORD DD 4×8。

3.搞清楚 DW ‘ba’,’c’,’e’ 中ba和c,e的存放是有区别,DW ‘AB’ 和 DB ‘AB’存放无区别

4.DUP 格式和代表的意义需要清楚

类型属性操作符

WORD PTR ;字类型

BYTE PTR ;字节类型

类型属性操作符仅是指定变量的“访问类型”,并不改变变量本身的类型。

在指令中用类型属性操作符指定对内存变量的访问类型,以匹配两个操作数。
OPER1 DB 3, 4
OPER2 DW 5678H, 9

MOV AX,OPER1 ;操作数类型不匹配
MOV BL, OPER2 ;操作数类型不匹配
MOV [DI], 0 ;操作数类型不明确
这三条指令可改为:
MOV AX,WORD PTR OPER1 ;从OPER1处取一个字使AX=0403H
MOV BL, BYTE PTR OPER2 ;从OPER2处取一个字节使BL=78H
MOV BYTE PTR[DI], 0 ;常数0送到内存字节单元

THIS操作符和LABEL伪操作

一个变量可以定义成不同的访问类型,THIS操作符或LABEL伪操作都可以实现。
格式:name = THIS type
格式:name LABEL type
操作:指定一个类型为type的操作数,使该操作数的地址与下一个存储单元地址相同。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
BUF=THIS WORD           
DAT DB 8,9
OPR_B LABEL BYTE
OPR_W DW 4 DUP(2)

MOV AX, 1234H
MOV OPR_B, AL
MOV OPR_W+2, AX
MOV DAT+1, AL
MOV BUF, AX
;表达式BUF=THIS WORD使BUF和DAT指向同一个内存单元。
;LABE伪操作使得OPR_B和OPR_W指向同一个内存单元。

BUF=THIS WORD
DAT DB 8,9
OPR_B LABEL BYTE
OPR_W DW 4 DUP(2)

这四句话定义完后,内存中数据如下:

08 09 02 00 02 00 02 00 02 00

BUF和DAT都指向08, OPR_B和OPR_W都指向第一个02

执行MOV OPR_B, AL 后 => 08 09 34 00 02 00 02 00 02 00

执行MOV OPR_W+2, AX 后 => 08 09 34 00 34 12 02 00 02 00

执行MOV DAT+1, AL后 => 08 12 34 00 34 12 02 00 02 00

执行MOV BUF, AX后 => 34 12 34 00 34 12 02 00 02 00

表达式赋值伪指令“EQU” 和=

可以用赋值伪操作给表达式赋予一个常量或名字。格式如下:
Expression_name EQU Expression
Expression_name = Expression

TIPS:

1.表达式中的变量或标号,必须先定义后引用

2.EQU伪操作中的表达式名是不允许重复定义的,而“=”伪操作则允许重复定义。

例如:VALUE =53

​ VALUE = VALUE + 89

这是正确的,但是=换成EQU就是错误的,因为EQU不允许重复定义。

汇编地址计数器$与定位伪指令

①地址计数器$

1.地址计数器是一个16位的变量,用$表示
2.开始汇编或在每一段开始时,将地址计数器初始化为零
3.当在指令中用到$时,它只代表此指令的首地址,而与$本身无关。

例如:jmp $+6 表示转向地址是JMP指令的首地址加上6

4.当$用在伪操作的参数字段时,它所表示的是地址计数器的当前值。

例如:

1
2
array dw 1,2,$+4,3,4,$+4, ;array分配的偏移地址 为0074。
[0074] 01,00 , 02,00, 7C,00, 03,00, 04,00, 82,00

7C=$(0078)+4=007C 后面同理,由于$的值在不断变换,因此两个$+4的值是不同的。

TIPS:

这里$每次+1的1代表一个字节,如果定义的数据类型是DW(字),$每次则要+2

②ORG 伪操作

ORG伪操作用来设置当前地址计数器的值

格式:ORG constant expression
操作:如常数表达式的值为n,则该操作指示下一个字节的存放地址为n。

例子:

1
2
3
4
5
6
ORG  0
DB 3
ORG 4
BUFF DB 6
ORG $+6
VAL DB 9

内存中数据存放: 03 — — — 06 — — — — — — 09

③EVEN 伪操作
EVEN伪操作使下一个变量或指令开始于偶数地址。

④ALIGN 伪操作
ALIGN伪操作使下一个变量的地址从4的倍数开始。

过程定义伪指令

过程定义包含两条伪指令:PROC和ENDP。

PROC表示过程的开始,ENDP表示过程的结束。

过程定义语句的格式:
过程名 PROC [属性] ;过程开始
;过程体
过程名 ENDP ;过程结束
功能:定义一个过程(子程序)。

属性可以是FAR或NEAR类型。NEAR为近,是段内调用。FAR类型为远,是跨段调用,缺省时为NEAR。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data    segment                 ;定义数据段data
string db ‘hello,world!$’
data ends
code segment ;定义代码段code
assume cs:code,ds:data
main proc far ;定义过程main
mov ax,data
mov ds,ax
mov dx,offset string
mov ah,9
int 21h
mov ah,4ch
int 21h
main endp
code ends
end main ;汇编结束, 程序起始点main

表达式和操作符

①算术操作符

算术运算符主要有+、-、*、/、MOD。

MOD也称为取模,它得到除法之后的余数。

减法运算可用于段内两个操作数地址(以变量名表示)的运算,其结果是一个常数,表示这两个变量之间相距的字节数。

TIPS:算术操作符的使用中,常量间可以用各种操作符,变量和常量有物理意义才能用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ORG 0
VAL=4
DA1 DW 6,2,9,3
DA2 DW 15,17,24
COU=$-DA2

MOV AX,DA1*4 ;错,地址乘或除,没有意义
MOV AX,DA1*DA2 ;错,地址乘或除,没有意义
MOV AX,DA1+DA2 ;错,地址相加,没有意义
MOV AX,BX+VAL ;错,BX+VAL须用指令实现
MOV AX,[BX+VAL] ;地址表达式,汇编成MOV AX,[BX+4]
MOV AX,DA1+VAL ;地址表达式,汇编成MOV AX,[4]
MOV AX,[DA1+VAL] ;地址表达式,汇编成MOV AX,[4]
MOV AX,VAL*4/2 ;数字表达式,汇编成MOV AX,8
MOV AX,[VAL*4/2] ;数字表达式,汇编成MOV AX,8
MOV CX,(DA2-DA1)/2 ;得到DA1区数据个数,汇编成MOV CX,4
MOV BX,COU ;得到DA2区的字节数,汇编成MOV BX,6

逻辑与移位操作符

逻辑操作符:AND, OR, NOT, XOR。
移位操作符:SHL和SHR。

格式:expression 操作符 number
逻辑与移位操作符都是按位进行的。
逻辑与移位操作符都只能用于数字表达式中。

1
2
3
4
5
6
ARY  DW  8
VAL=4
MOV AX,BX AND 0FFH ;错,BX AND VAL须用指令实现
MOV AX,ARY AND 0FFH ;错,ARY AND VAL须用指令实现
MOV AX,VAL AND 0F0H ;汇编成MOV AX,0
AND AX,VAL OR 0F0H ;汇编成AND AX,0F4H
1
2
3
4
5
6
7
ARY  DW   8
VAL=4
MOV AX,BX SHL 2 ;错,BX 左移须用指令实现
MOV AX,ARY SHL 2 ;错,ARY 左移须用指令实现
MOV AX,VAL SHL 2 ;汇编成MOV AX,10H
MOV AX,8 SHL 2 ;汇编成MOV AX,20H
MOV AX,VAL SHL 15 ;汇编成MOV AX,00H

关系操作符

关系操作符用来对两个操作数的大小关系作出判断。
EQ(相等)
NE(不相等)
LT(小于)
LE(小于等于)
GT(大于)
GE(大于等于)

1
2
3
4
VAL=4
MOV AX,BX GT 2 ;错,BX 是否大于2须用指令实现判断
MOV AX,VAL GE 2 ;汇编成MOV AX,FFFFH
MOV AX,8 LE VAL ;汇编成MOV AX,0

数值回送操作符

①TYPE

格式:TYPE expression
表达式为变量,则汇编程序回送该变量的以字节数表示的类型。
DB 回送1 DW 回送2
DD 回送4 DF 回送6
DQ 回送8 DT 回送10
表达式为标号,则汇编程序回送代表该标号类型的数值。
NEAR 回送-1
FAR 回送-2
表达式为常数则回送0。

②LEN

格式: LENGTH variable

若变量用DUP定义,则返回总变量数,否则为1。

嵌套的DUP不计。所以,对于使用嵌套的DUP复制的数据不能据此得到正确的总变量数。

例如:

BUFF DW 4DUP(4DUP(3))

LEN BUFF 的值是4

③SIZE

格式:SIZE variable

若变量用DUP定义,则返回总字节数,否则为单个变量的字节数。

嵌套的DUP不计,所以,对于使用嵌套的DUP复制的数据不能据此得到正确的总字节数。

④OFFSET

格式:OFFSET variable或label
操作:回送变量或标号的偏移地址。

⑤SEG

格式:SEG variable或label
操作:回送变量或标号的段地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ORG  0
VAL=4
ARR DW 4 DUP(3)
BUF DW 4 DUP( 4 DUP(3))
DAT DW 15,17,24
STR DB ‘ABCDEF’
;汇编程序对下面的指令汇编结果为:
MOV AX,TYPE ARR ;汇编成MOV AX,2
MOV AX,LENGTH ARR ;汇编成MOV AX,4
MOV AX,LENGTH BUF ;汇编成MOV AX,4
MOV AX,LENGTH DAT ;汇编成MOV AX,1
MOV AX,SIZE ARR ;汇编成MOV AX,8
MOV AX,SIZE BUF ;汇编成MOV AX,8(不是32)
MOV AX,SIZE DAT ;汇编成MOV AX,2
MOV AL,SIZE STR ;汇编成MOV AX,1
MOV AX,OFFSET ARR ;不完整的机器指令
MOV BX,SEG ARR ;不完整的机器指令

Ch7-分支与循环程序设计

单分支结构程序

aEYwOe.png

例题:

双字长数存放在DX和AX寄存器中(高位在DX),求该数的绝对值(用16位指令)。

算法分析:

  1. 双字长数高字在DX中,低字在AX中;
  2. 判该数的正负,为正数(最高位为0),该数不处理;为负数,就对该数求补(即反码加1)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DATAS SEGMENT
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov dx,9000h
mov ax,1234h

cmp dx,8000h
jb exit
not ax
not dx
add ax,1
adc dx,0
exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START

复合分支程序

例题:

从键盘输入一位十六进制数,并将其转换为十进制数输出显示。

算法分析:
从键盘输入一个十六进制数,有以下四种情况:

    1. 为数字0~9(ASCII码30~39H),无需处理,直接输出;
    2. 为大写字母A~F(ASCII码41~46H),先输出31H,再输出该数ASCII码-11H;
    3. 为小写字母a~f(ASCII码61~66H),先输出31H,再输出该数ASCII码-31H;
    4. 该数不为0~9、A~F、a~f,是非法字符,应退出程序或输出错误信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
DATAS SEGMENT
mess1 db 'yu bao zui shuai $'
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov ah,1
int 21h
cmp al,30h
jl error
cmp al,39h
jle num
cmp al,41h
jl error
cmp al,46h
jle bigletter
cmp al,61h
jl error
cmp al,66h
jle smallletter
jmp error
num:
mov ah,2
mov dl,al
int 21h
jmp exit
bigletter:
mov ah,2
mov dl,'1'
push ax
int 21h ;int指令会改写ax
pop ax
mov ah,2
sub al,11h
mov dl,al
int 21h
jmp exit
smallletter:
mov ah,2
mov dl,'1'
push ax
int 21h
pop ax
mov ah,2
sub al,31h
mov dl,al
int 21h
jmp exit
error:
mov ah,2
mov dl,'N'
int 21h
exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START

多分支程序

分支向量表:

如果在分支结构中有超过两个以上的多个可供选择的分支,这就是多分支结构。

如果对多分支的条件逐个查询以确定是哪一个分支,只会增加代码和时间,为了尽快进入某个分支,可以采用分支向量表法。

例题:

根据键盘输入的一位数字(1~4),使程序转移到4个不同的分支中去,以显示键盘输入的数字。

算法分析:从键盘输入一个数1~4,

   1. 建立一个分支向量表branch ,集中存放四个分支的偏移地址;
   2. 每个偏移地址位16位,占用2个单元;
   3. 四个分支的偏移地址在转移地址表的地址是:转移地址表首址+输入数字(0~3)×2;
   4. 用间接寻址方式转向对应分支。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
DATAS SEGMENT
DATAS ENDS

STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov ah,1
int 21h
mov dl,al
cmp al,31h
jl exit
cmp al,34h
jnle exit
sub al,30h
dec al
shl bl,1
mov bh,0
jmp branch[bx]
r1:
mov ah,2
int 21h
jmp exit
r2:
mov ah,2
int 21h
jmp exit
r3:
mov ah,2
int 21h
jmp exit
r4:
mov ah,2
int 21h
exit:
MOV AH,4CH
INT 21H

branch dw r1 ;向量分支表
dw r2
dw r3
dw r4

CODES ENDS
END START

循环计数程序

例题:

把BX中的二进制数用十六进制显示.(设BX=123AH)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
DATAS SEGMENT
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov bx,123AH
mov cx,4
shift:
rol bx,1
rol bx,1
rol bx,1
rol bx,1
mov al,bl
and al,0fh
add al,30h
cmp al,39h
jle disp1
jmp disp2
disp1:
mov dl,al
mov ah,2
int 21h
loop shift
disp2:
sub al,10
add al,'A'-'0'
mov dl,al
mov ah,2
int 21h
loop shift

MOV AH,4CH
INT 21H
CODES ENDS
END START

条件循环程序

在循环程序中,有时候每次循环所做的操作可能不同,即循环体中有分支的情况,需要依据某一个标志来决定做何操作。标志位为1表示要做操作A,标志位为0表示要做操作B,我们可把这种标志字称为逻辑尺。

例题:

从键盘输入8位二进制数作为逻辑尺。再输入一个英文字母,根据逻辑尺当前的最高位标志显示输出该字母的相邻字符,标志位为0则显示其前趋字符,标志位为1则显示其后继字符。显示相邻字符后,逻辑尺循环左移一位,再接收下一个字母的输入,并依据逻辑尺显示相邻字符,直到回车键结束程序。

新转跳符:JNC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
DATAS SEGMENT
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov bx,0
mov cx,8
inlog:
mov ah,1
int 21h
cmp al,30h
jb exit
cmp al,31h
ja exit
sub al,30h
shl bl,1
add bl,al
loop inlog
mov ah,2 ;输出一个回车,换到下一行
mov dl,10
int 21h
inchar:
mov ah,1
int 21h
cmp al,13 ;检查是否为回车键,如果是则退出
je exit ;
mov dl,al
rol bl,1
jnc k30 ;jnc的意思是如果没进位则转跳,这里由于做了rol bl,1。即bl首位如果是0则转跳k30
inc dl ;否则找后继字符
jmp putc
k30:
dec dl
putc:
mov ah,2
int 21h
jmp inchar
exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START

条件计数循环程序

例题:

设置键盘缓冲区为16个字节,从键盘输入一串字符,然后再从键盘输入一个单个字符,查找这个字符是否在字符串中出现,如果找到,显示该字符串,否则显示‘NOT FOUND’。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
DATAS SEGMENT
buffer db 16,?,16 dup(?),13,10,'$'
input db 'intput string:$'
notfound db 'notfound$'
inchar db 'input char:$'
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX

mov ah,9
lea dx,input
int 21h
lea dx,buffer
mov ah,10
int 21h
mov dl,10
mov ah,2
int 21h
mov ah,9
lea dx,inchar
int 21h
mov ah,1
int 21h
mov bl,al
mov dl,10
mov ah,2
int 21h
lea di,buffer+1
mov cl,buffer+1
seek:

inc di
cmp bl,[di]
jz disp
loop seek
jmp nofind
disp:
mov ah,9
lea dx,buffer+2
int 21h
jmp exit
nofind:
mov ah,9
lea dx,notfound
int 21h
exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START

Ch8-子程序设计

子程序结构

①子程序调用指令

子程序定义:在模块化程序设计中,经常把程序中某些具有独立功能的部分编写成独立的程序模块,称为子程序。
主程序通过CALL指令调用子程序
子程序执行完毕后通过RET指令回到主程序

CALL调用指令:

格式: CALL DST
操作:首先把下一条指令的地址(返回地址)压入堆栈保存,再把子程序的入口地址置入IP(CS)寄存器,以便实现转移。
对于段内调用,只是向堆栈保存IP寄存器的值。(段内CS相同不需要记录)
对于段间调用,是先向堆栈保存CS寄存器的值,再向堆栈保存IP寄存器的值。

RET返回指令:

格式1: RET
格式2: RET EXP
操作:把堆栈里保存的返回地址送回IP(CS)寄存器,实现程序的返回。
对于段内调用,弹出一个字到IP寄存器
对于段间调用,先弹出一个字到IP寄存器,再 弹出一个字到CS寄存器

ae58mT.png

解析:这个程序就是A调用B,然后B在调用C,需要注意的是,A调用B是段间调用,所以要CALL FAR PTR B,而B调用C是段内调用,要CALL NEAR PTR C。段间调用要压栈保护CS和IP,而段内调用只需要压栈保护IP即可。

aeImDK.png

②过程定义与过程结构

过程定义伪指令
Procedure_Name PROC Attribute

Procedure_Name ENDP

如:
main proc far /near

main endp

NEAR为近,是段内调用。FAR类型为远,是跨段调用。
如调用程序和子程序在同一代码段,则使用NEAR属性;如调用程序和子程序不在同一代码段,则使用FAR属性。
主程序的过程定义属性应为FAR。,因为dos首先会调用主程序main,因此第一次一定是远调

基本定义结构如下:

1.子程序和调用程序在一个代码段

1
2
3
4
5
6
7
8
9
10
11
main proc far	

call subr

ret
main endp

subr proc near ;子程序和调用程序在一个代码段,所以near

ret
subr endp

2.子程序和调用程序不在一个代码段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
code1 segment 

main proc far

call subr

ret
main endp
code1 ends



code2 segment

call subr

subr proc far

ret
subr endp
code2 ends

③ 保存和恢复现场寄存器

子程序调用 CALL:首先将返回地址压栈,然后把子程序的入口地址送入IP/CS寄存器。
子程序返回 RET:将堆栈里保存的返回地址送回IP/CS寄存器。
在子程序中对主程序的现场实施保护和恢复
在进入子程序后,对将要使用的寄存器,先保存这些寄存器的值,在子程序退出前恢复这些寄存器的值

amJUIJ.png

这里要注意 你压入的顺序和弹出的顺序是相反的。

子程序的参数传递

入口参数(调用参数):主程序传递给子程序。
出口参数(返回参数):子程序返回给主程序。
传递的参数:值传递和地址传递。

①用寄存器传递参数

用寄存器传递参数就是约定某些寄存器存放将要传递的参数。该方法简单,执行的速度也很快。但由于寄存器数量有限,不能用于传递很多的参数。

main中的三句话很重要,为的是可以正常返回,结束程序

main proc far
push ds
xor ax,ax
push ax

例题:

从键盘输入一个十进制数(小于65536的正数),显示输出该数的十六进制形式。通过寄存器传送变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
dtohex segment
assume cs:dtohex
main proc far
push ds ;push ds
xor ax,ax
push ax ;push 0,使得栈中压入ds:0弹出后程序即可结束,这三句话很重要,经常考填空
call dtob ;十进制数键盘输入整合为二进制
call crlf ;输出回车换行
call btoh ;二进制转为十六进制显示
ret
main endp
;-------------------------------------------
dtob proc near
mov bx,0
intput:
mov ah,1
int 21h
sub al,30h
jl exit
cmp al,9
jg exit
cbw
xchg ax,bx
mov cx,10
mul cx
xchg ax,bx
add bx,ax
jmp input
exit:
ret
dtob endp
;----------------------------------------------
btoh proc near
mov ch,4
shift:
mov cl,4
rol bx,cl
mov al,bl
and al,0fh
add al,30h
cmp al,39h
jle dig
add al,7
dig:
mov dl,al
mov ah,2
int 21h
dec ch
jnz shift
ret
btoh endp

②用变量传递参数

参数较多时可以用约定的变量在过程间传递参数。

例题:

键盘输入字符串到缓冲区后,对缓冲区内容降序排序并输出。

请仔细观察双循环的写法,十分重要十分重要十分重要十分重要十分重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
DATAS SEGMENT
buff db 16
numb db ?
arry db 16 dup(?)
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
main proc far
push ds
sub ax,ax
push ax

mov ax,datas
mov ds,ax
call order
ret
main endp

order proc near
lea dx,buff
mov ah,10
int 21h
mov cl,numb
mov ch,0
mov di,cx
lp1:
mov cx,di
mov bx,0
lp2:
mov al,arry[bx]
cmp al,arry[bx+1]
jnb nxt
xchg al,arry[bx+1]
mov arry[bx],al
nxt:
inc bx
loop lp2
dec di
jnz lp1
call output
ret
order endp

output proc near
mov bl,numb
mov bh,0
mov byte ptr[arry+bx],'$'
lea dx,arry
mov ah,9
int 21h
ret
output endp

CODES ENDS
END main

③用地址表传递参数

在主程序中建立一个地址表,把要传递的参数地址放在地址表中,然后把地址表的首地址放入寄存器,子程序通过寄存器间接寻址方式从地址表中取得所需参数,可以设计通用子程序处理其他类似字符串排序问题。

例题:

采用通过地址表传递参数地址的方法,键盘输入缓冲区并对其内容排序和输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
DATAS SEGMENT
buff db 16
numb db ?
arry db 16 dup(?)
table dw 3 dup(?);地址表
DATAS ENDS

STACKS SEGMENT
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
main proc far
push ds
sub ax,ax
push ax

mov ax,datas
mov ds,ax
mov table,offset buff ;这里要注意,不可以用LEA table,buff ,因为lea后面要接寄存器,不是寄存器不能用,只可以lea bx,buff这样用。
mov table+2,offset numb
mov table+4,offset arry
mov bx,offset table
mov si,bx
call order
ret
main endp

order proc near
mov dx,[bx]
mov ah,10
int 21h
mov di,[bx+2]
mov cl,[di]
mov ch,0
mov di,cx
lp1:
mov cx,di
mov bx,[si]
add bx,2
lp2:
mov al,[bx]
cmp al,[bx+1]
jbe nxt
xchg al,[bx+1]
mov [bx],al
nxt:
inc bx
loop lp2
dec di
jnz lp1

call output
ret
order endp

output proc near
mov bl,numb
mov bh,0
mov byte ptr[arry+bx],'$'
lea dx,arry
mov ah,9
int 21h
ret
output endp

CODES ENDS
END main

④用堆栈传递参数

例题:

键盘输入缓冲区内容排序并输出,用堆栈传递参数地址。

提示:会考这三句设置ss和sp

1
2
3
4
;设置ss和sp,很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要
mov ax,data
mov ss,ax
lea sp,tos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
data segment 
dw 50 dup(?) ;堆栈50个字
tos label word ;栈顶地址tos
buff db 16
numb db ?
arry db 16 dup(?)
data ends
code segment
assume cs:code,ds:data,ss:data
main proc far
;设置ss和sp,很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要很重要
mov ax,data
mov ss,ax
lea sp,tos
;ds和0压入堆栈,以便返回dos
push ds
xor ax,ax
push ax
mov ax,data
mov ds,ax
;参数地址压入堆栈
lea bx, buff
push bx ;buff的地址压入堆栈
lea bx, numb
push bx ;numb的地址压入堆栈
lea bx, arry
push bx ;arry的地址压入堆栈
call order
ret
main endp

order proc near
mov bp,sp
mov dx,[bp+6] ;buff地址送dx
mov ah,10
int 21h
mov di, [bp+4] ;取numb的地址
mov cl,[di]
mov ch,0 ;numb送cx
mov di,cx
lp1:
mov cx,di
mov bx,[bp+2] ;arry的地址送bx
lp2:
mov al,[bx]
cmp al,[bx+1]
jge cont
xchg al,[bx+1]
mov [bx],al
cont:
inc bx
loop lp2
dec di
jnz lp1
call output
ret 6 ;修改sp指针并返回
order endp

;------------------
output proc near
mov di,[bp+4] ;后面插入$以便显示
mov bl,[di]
mov bh,0
mov di,[bp+2]
mov byte ptr[di+bx],'$'
mov dx, di
mov ah,9
int 21h
ret
output endp
code ends
end main

⑤用结构变量传递参数

结构就是把逻辑上互相关联的一组数据以某种形式组合在一起。在程序中,若要多次使用相同的一组数据格式,那么我们就可以把这一组数据格式定义为一个结构数据。

结构类型的定义:结构名 STRUC
……
结构名 ENDS
STRUC伪指令只是定义了一种结构模式,还没有生成结构变量。
用结构预置语句生成结构变量并赋值。
结构预置语句格式:
变量 结构名 <各字段赋值>

对结构字段初值的修改,并非所有字段的初值都可以修改,只有简单结构字段和字符串字段初值才可以修改。简单结构字段是指由伪指令DB、DW或DD定义的单项变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;定义一个名为STUDENT的结构类型。
STUDENT STRUC
ID DB ‘AAAAAAAA’ ;可修改
NAME DB 3 DUP(0) ;不可修改
JF1 DW 22H ;可修改
JF2 DW ? ;可修改
JF3 DW ? ;可修改
JF4 DW ? ;可修改
STUDENT ENDS

;声明结构体变量
STD1 STUDENT <’A2031456’,,,33H> ;定义一个结构体student并赋初值
STD2 STUDENT < > ;定义一个结构体student
STDSS STUDENT 100 DUP(< >) ;定义100个student结构体


;结构变量的访问
MOV SI,1
LEA BX,STD1
MOV AL, STD1.NAME[SI]
; 变量STD1的字段NAME的第2项送AL
MOV AL, [BX].NAME[SI]
; 变量STD1的字段NAME的第2项送AL
MOV DL, STDSS+3*19.NAME[SI]
;变量STDSS第4条记录的字段NAME的第2项送AL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
data  segment 
dw 50 dup(?) ;堆栈50个字
tos label word ;栈顶地址tos
buff db 16
numb db ?
arry dw 16 dup(?)
data ends
code segment
assume cs:code,ds:data,ss:data

main proc far
;设置ss和sp,设置栈和栈顶指针
mov ax,data
mov ss,ax
lea sp,tos
;ds和0压入堆栈,以便返回dos
push ds
xor ax,ax
push ax
mov ax,data
mov ds,ax
;参数地址压入堆栈
lea bx, buff
push bx ;buff的地址压入堆栈
lea bx, numb
push bx ;numb的地址压入堆栈
lea bx, arry
push bx ;arry的地址压入堆栈
call order
ret
main endp

order proc near
par struc ;定义结构体
pip dw ?
p3 dw ?
p2 dw ?
p1 dw ?
par ends ;完成结构体定义
mov bp,sp
mov dx,[bp].p1 ;buff的地址送dx
mov ah, 10
int 21h
mov di, [bp].p2 ;取numb的地址
mov cl,[di ;numb送cx
mov ch,0
mov di,cx
lp1:
mov cx,di
mov bx,[bp].p3 ;arry地址送bx
lp2:
mov al,[bx]
cmp al,[bx+1]
jge cont
xchg al,[bx+1]
mov [bx],al
cont:
inc bx
loop lp2
dec di
jnz lp1
call output
ret 6 ;修改sp指针并返回
order endp
code ends
end main

;------------------
output proc near
mov di,[bp].p2
mov bl,[di]
mov bh,0
mov di,[bp].p3
mov byte ptr[di+bx],'$'
mov dx, di
mov ah,9
int 21h
ret
output endp
code ends
end main

多模块程序设计

汇编程序是可以由两个asm文件构成的。

1
2
3
;812main.asm
public buff,numb,arry ;如果你的程序想把这些变量给其他程序用。一定要public生命这些变量
extrn order:far ;如果你用了别人的程序里的变量 要extern声明
1
2
3
812sub.asm
public order
extrn buff:byte,numb:byte,arry:byte

各模块先分别汇编,然后再连接:
Link 812main+812sub

Link 的次序影响结果,主模块在前面。

Ch9-宏汇编

宏定义,宏调用,宏展开

宏是源程序中一段有独立功能的程序代码。它只需要在源程序中定义一次,就可以多次调用,调用时只需要用一个宏指令语句就可以了。

宏功能既可以实现程序复用,又能方便的传递多个参数

宏定义:

宏指令名 MACRO [形参1,形参2,…]
<宏定义体>
ENDM

TIPS:

1.宏定义体是一组有独立功能的程序代码。

2.宏指令名给出宏定义的名称,调用时就使用宏指令名来调用宏定义第一个符号必须是字符

3.哑元表给出了宏定义中所用到的形式参数,每个哑元之间用逗号隔开。

例题:用宏指令实现两数的相加。

1
2
3
4
5
6
7
8
9
10
11
12
13
;宏定义:
sumn MACRO x, y, result
mov ax, x
add ax, y
mov result, ax
ENDM
;宏调用
sumn 34,25,bx

;宏展开
mov ax,34
add ax,25
mov bx,ax

例题:用宏指令实现两个八位有符号数的乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;宏定义
imultiply MACRO x,y,result
push ax ;这个是为了保护进来的时ax的值,退出宏后ax值依旧,如果没这个需求不加也可以。
mov al,x
imul y
mov result,ax
pop ax
ENDM

;宏调用
imultiply cl,dl,[bx]
imultiply ary,var,save

;宏展开
push ax
mov al,cl
imul dl
mov [bx], ax
pop ax

push ax
mov al,ary
imul var
mov save,ax
pop ax

例题:

某工厂工人的周工资由计时工资和计件工资组成,计时工资按每小时工资率RATE乘以工作小时数计算;计件工资按超定额部分乘以SUP计算(超定额=实际完成的工件数MADE-定额工件数PART),工资总额放在WAGE中。

1
2
3
4
5
6
7
8
9
10
11
12
13
;宏定义
wages macro rate, hout, made, part, sup
wage=rate*hout+(made-part)*sup
endm
rate=5
part=100
sup=4

;宏调用:
wages rate,40,120,part,sup

;宏展开为:
wage=rate*40+(120-part)*sup

宏定义的嵌套

这种嵌套结构的特点是外层宏定义的宏体中又有宏定义,只有调用外层宏定义一次后,才能调用内层宏指令

例题:

用嵌套的宏定义实现两个八位数的算术运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
;宏定义:
math MACRO mathname, action, num
mathname MACRO x, y, result
push ax
mov num,x
action y
mov result ax
pop ax
ENDM
ENDM
;宏调用
math divide, div, ax

;宏展开
divide MACRO x, y, result
push ax
mov ax, x
div y
mov result, ax
pop ax
ENDM
;接下来可使用宏调用:
divide ary, var, save

;则宏展开如下:
push ax
mov ax, ary
div var
mov save, ax
pop ax

宏定义中使用宏调用

宏定义中使用的宏调用必须已经定义。

1
2
3
4
5
6
7
8
9
10
;用宏指令实现显示字符。
;宏定义:
INT21 MACRO FUNCTN
MOV AH,FUNCTN
INT 21H
ENDM
DISPC MACRO CHAR
MOV DL,CHAR
INT21 2
ENDM