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(字)。
高位字节/低位字节:
上图中的字和字节在内存中表示一定要明白:
(31200H)处的字包含两个字节A28FH,分别在31201H,31200H,因为字的前半部分是高位字节在高位,但是字的位置是由低位字节表示的。
8086CPU有20位地址总线,可以传送20位地址,也就是说物理地址20位。20位地址可以标定的内存单元有1M,即寻址能力可达1MB。
存储器分段:
物理地址=段地址*16+偏移地址
每个段大小位64KB(偏移地址从0000-FFFF)。
段的类型:
代码段—用于存放指令,代码段段基址存放在段寄存器CS
数据段—用于存放数据,数据段段基址段地址存放在段寄存器DS
附加段—用于辅助存放数据,附加段段基址存放在段寄存器ES
堆栈段—是重要的数据结构,可用来保存数据、地址和系统参数,堆栈段段基址存放在段寄存器SS
写程序时,代码段必须要有。
逻辑地址:
逻辑地址是用户编程时使用的地址,分为段地址和偏移地址两个部分。
逻辑地址:如上如中存储单元C8的逻辑地址为: 1123H:0013H 或者 1124H:0003H
公式:段地址*16+偏移地址=物理地址 ( 相当于短地址在十六进制下左移一位再加上偏移地址即为物理地址)
例题 段基址为1896H,偏移地址为1655H。其物理地址为多少?
18960H+1655H=19FB5H
通用寄存器:
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 |
|
显示字符串:
1 |
|
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 |
|
等待从键盘输入一个字符,将该字符的ASCII码送入AL中,并送屏幕显示。
2.显示单个字符(2号功能)
1 |
|
显示DL的字符,执行后AL寄存器的值被修改为DL的值
3.显示字符串(9号功能)
1 |
|
DS:DX指向以$结束的字符串STR,执行后AL寄存器的值被修改为$
4.键盘输入到缓冲区(0AH/10号功能)
1 |
|
1 |
|
5.结束程序返回DOS(4CH号功能)
1 |
|
Ch4-操作数的寻址方式
1.立即寻址方式
操作数就在指令中,紧跟在操作码之后,操作数作为指令的一部分存放在代码段。
1 |
|
TIPS:
①执行时无需去内存取数,因此称为立即数。
②主要用于寄存器赋初值。
③立即数只能作为源操作数,并且长度与目的操作数一致。 不可出现MOV AL,12AFH
2.寄存器寻址方式
操作数就是寄存器中的值。指令中给出寄存器的名字
1 |
|
3.直接寻址方式
操作数的有效地址EA就在指令中,机器默认段地址在DS中。
EA和段寄存器中的段地址组成操作数。
1 |
|
4.寄存器间接寻址方式
特点:操作数的有效地址在寄存器中,只允许使用BX、BP、SI和DI寄存器。
物理地址=10H × (DS) + (BX)
物理地址=10H × (DS) + (SI )
物理地址=10H × (DS) + (DI )
物理地址=10H × (SS) + (BP)
1 |
|
5.寄存器相对寻址方式
特点:操作数的有效地址是一个寄存器和位移量之和。
物理地址=10H × (DS) + (BX) + 8(16)位位移量
物理地址=10H × (DS) + (SI ) + 8(16)位位移量
物理地址=10H × (DS) + (DI ) + 8(16)位位移量
物理地址=10H × (SS) + (BP) + 8(16)位位移量
1 |
|
1 |
|
1 |
|
6.基址变址寻址方式
操作数的有效地址是一个基址寄存器和一个变址寄存器的内容之和。
基址寄存器BX和BP,变址寄存器SI和DI。
1 |
|
1 |
|
7.相对基址变址寻址方式
操作数的有效地址是一个基址寄存器和一个变址寄存器以及一个位移量之和。
基址寄存器BX和BP,变址寄存器SI和DI。
默认段寄存器搭配和寄存器间接寻址方式一样
1 |
|
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 |
|
两个操作数不能同为内存单元,例如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 |
|
④XCHG 交换指令
格式: XCHG OPR1, OPR2
操作: (OPR1)<==>(OPR2)
功能:把两个操作数互换位置。
遵循双操作数指令的规定,但操作数不能为立即数。
1 |
|
累加器专用传送指令
① 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 |
|
②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 |
|
③XLAT换码指令
格式:XLAT
操作:AL←(BX+AL)
功能:把BX+AL的值作为有效地址,取出其中的一个字节送AL。
地址传送指令
①LEA 有效地址送寄存器指令
格式:LEA REG,SRC
操作:REG←SRC
功能:把源操作数的有效地址EA送到指定的寄存器。
1 |
|
1 |
|
②LDS 指针送寄存器和DS指令
格式: LDS REG, SRC
操作:REG←(SRC)
DS←(SRC+2)
功能:把源操作数SRC所指向的内存单元中的两个字送到指定的寄存器REG和DS。
1 |
|
③LES 指针送寄存器和ES指令
格式: LES REG, SRC
操作:REG←(SRC)
ES←(SRC+2)
功能:把源操作数SRC所指向的内存单元中的两个字送到指定的寄存器REG和ES。
1 |
|
标志寄存器传送指令
LAHF 标志寄存器FLAGS的低字节送AH
SAHF AH送FLAGS的低字节
PUSHF 标志进栈
POPF 标志出栈
以上传送类指令均不影响标志位,除SAHF, POPF 外.
1 |
|
算术运算指令
①CBW 扩展指令
AL扩展为AX ,字节扩展为字
扩展为符号扩展
②CWD 扩展指令
AX扩展为DX,AX 字扩展为双字
扩展为符号扩展
负数补1,正数补0
1 |
|
1 |
|
③ADD 加法
格式:ADD DST,SRC
操作:(DST)← (DST) + (SRC)
加法指令执行后会影响标志寄存器中的CF和OF标志位
无符号数的溢出标志位CF(Carry Flag)
1 |
|
有符号数的溢出标志位OF(overflow flag)
1 |
|
④ADC 带进位加法指令
格式:ADC DST,SRC
操作:(DST)←(DST)+(SRC)+ CF
1 |
|
⑤INC 自增加一
格式:INC OPR
操作:(OPR)←(OPR)+1
条件标志位(条件码) 最主要有:
进位CF, 零ZF, 符号SF, 溢出OF
⑥SUB 减法指令
格式:SUB DST,SRC
操作:(DST)← (DST) -(SRC)
1 |
|
⑦SBB 带借位减法指令
格式:SBB DST,SRC
操作:(DST)← (DST) -(SRC)- CF
1 |
|
⑧DEC 自减1指令
格式:DEC OPR
操作:(OPR)←(OPR)- 1
⑨NEG 求补指令
格式:NEG OPR
操作:(OPR)← -(OPR)
功能:对OPR求补,求- OPR, 即反码+1.
只有OPR为0时,CF=0。
1 |
|
⑩CMP比较指令
格式:CMP OPR1, OPR2
操作:(OPR1)-(OPR2)
不回送结果,只产生标志位。
1 |
|
乘法/除法指令
①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 |
|
③DIV 无符号数触发
格式:DIV SRC
操作取决于SRC的大小
字节操作: AL<= AX / SRC 的商
AH<=AX / SRC 的余数
字操作: AX<= (DX,AX) / SRC
DX<=(DX,AX) / SRC 的余数
1 |
|
④IDIV 带符号数除法指令
格式: IDIV SRC
操作与DIV 相同
余数和被除数同符号。
被除数长度应为除数长度的两倍。
SRC不能是立即数,因为要根据SRC的类型来确定操作类型。
算术运算综合题:
重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点
算术运算综合举例,计算:(V-(X×Y+Z-16))/X,其中X、Y、Z、V均为16位带符号数,在数据段定义,要求上式计算结果的商存入AX,余数存入DX寄存器。
1 |
|
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 |
|
②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 |
|
位移指令
①SHL 逻辑左移
格式:SHL OPR, CNT
CNT可以是1或CL寄存器,如需移位的次数大于1,则可以在该移位指令前把移位次数先送到CL寄存器。
②SAL 算数左移
③SHR 逻辑右移
④SAR 算术右移
⑤ROL循环左移
⑥ROR 循环右移
TIPS:以上所说的移动一位,指的是二进制的一位,并非十六进制的一位
1 |
|
串操作指令
①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 |
|
②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 |
|
③SCAS 串扫描
SCAS DST ;操作数寻址方式固定
SCASB ;字节
SCASW ;字
字节操作:
AL-(ES:DI), DI=DI±1
字操作:
AX-(ES:DI), DI=DI±2
指令不保存结果,只是根据结果设置标志位。
例题:
在附加段中有一个字符串MESS,其长度为19,要求查找其中有无空格符,若有空格符,把首次发现的空格符改为‘#’,存回该单元,并显示‘Y’,否则显示‘N’。编制程序如下所示
1 |
|
④STOS 存入串
STOS DST ;操作数寻址方式固定
STOSB ;字节
STOSW ;字
字节操作:
(ES:DI)←AL, DI=DI±1
字操作:
(ES:DI)←AX, DI=DI±2
例题:
写出把附加段EXT中的首地址为MESS,长度为9个字的缓冲区置为0值的程序片段。
1 |
|
⑤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 |
|
3.段间直接转移
格式: JMP FAR PTR OPR
操作: IP←OPR的偏移地址
CS←OPR所在段的段地址
4.段间间接转移
格式: JMP DWORD PTR OPR
操作: IP←(EA)
CS←(EA+2)
可以使用除立即数和寄存器方式以外的任何一种寻址方式。
1 |
|
②条件转移指令
条件转移指令根据上一条指令所设置的标志位来判别测试条件,从而决定程序转向。
通常在使用条件转移指令之前,应有一条能产生标志位的前导指令,如CMP指令。
汇编指令格式中,转向地址由标号表示。
所有的条件转移指令都不影响标志位。
1 |
|
例题:
有一个长为19字节的字符串,首地址为MESS。查找其中的‘空格’(20H) 字符,如找到则继续执行,否则转标号NO。
1 |
|
循环指令
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 |
|
Ch6-伪指令和源程序格式
伪指令概述
指令是在程序运行期间由计算机的CPU来执行的。
伪指令是在汇编程序对源程序进行汇编期间由汇编程序处理的操作。
段定义伪指令格式:
1 |
|
ASSUME伪指令格式:
1 |
|
数据定义与存储器单元分配伪指令
定义数据类型的伪指令:
DB:用来定义字节,其后的每个操作数都占用1个字节。
DW:用来定义字,其后的每个操作数都占用1个字。
DD:用来定义双字,其后的每个操作数都占用2个字。
DF:用来定义六个字节的字,其后的每个操作数都占用48位。
DQ:用来定义4个字,其后的每个操作数都占用4个字。
DT:用来定义10个字节,其后的每个操作数都占用10个字节。
DUP 复制伪指令
格式:count DUP (operand, …, operand)
操作:将括号中的操作数重复count次,count可以是一个表达式,其值应该是一个正数。
DUP操作可嵌套。
例子1:
1 |
|
数据在内存中的存放如下:
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 |
|
数据在内存中的存放如下:
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的存放是有区别的。
例子3:
用操作符复制操作数。
1 |
|
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 |
|
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 |
|
7C=$(0078)+4=007C 后面同理,由于$的值在不断变换,因此两个$+4的值是不同的。
TIPS:
这里$每次+1的1代表一个字节,如果定义的数据类型是DW(字),$每次则要+2
②ORG 伪操作
ORG伪操作用来设置当前地址计数器的值。
格式:ORG constant expression
操作:如常数表达式的值为n,则该操作指示下一个字节的存放地址为n。
例子:
1 |
|
内存中数据存放: 03 — — — 06 — — — — — — 09
③EVEN 伪操作
EVEN伪操作使下一个变量或指令开始于偶数地址。
④ALIGN 伪操作
ALIGN伪操作使下一个变量的地址从4的倍数开始。
过程定义伪指令
过程定义包含两条伪指令:PROC和ENDP。
PROC表示过程的开始,ENDP表示过程的结束。
过程定义语句的格式:
过程名 PROC [属性] ;过程开始
;过程体
过程名 ENDP ;过程结束
功能:定义一个过程(子程序)。
属性可以是FAR或NEAR类型。NEAR为近,是段内调用。FAR类型为远,是跨段调用,缺省时为NEAR。
1 |
|
表达式和操作符
①算术操作符
算术运算符主要有+、-、*、/、MOD。
MOD也称为取模,它得到除法之后的余数。
减法运算可用于段内两个操作数地址(以变量名表示)的运算,其结果是一个常数,表示这两个变量之间相距的字节数。
TIPS:算术操作符的使用中,常量间可以用各种操作符,变量和常量间有物理意义才能用
1 |
|
逻辑与移位操作符
逻辑操作符:AND, OR, NOT, XOR。
移位操作符:SHL和SHR。
格式:expression 操作符 number
逻辑与移位操作符都是按位进行的。
逻辑与移位操作符都只能用于数字表达式中。
1 |
|
1 |
|
关系操作符
关系操作符用来对两个操作数的大小关系作出判断。
EQ(相等)
NE(不相等)
LT(小于)
LE(小于等于)
GT(大于)
GE(大于等于)
1 |
|
数值回送操作符
①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 |
|
Ch7-分支与循环程序设计
单分支结构程序
例题:
双字长数存放在DX和AX寄存器中(高位在DX),求该数的绝对值(用16位指令)。
算法分析:
- 双字长数高字在DX中,低字在AX中;
- 判该数的正负,为正数(最高位为0),该数不处理;为负数,就对该数求补(即反码加1)。
1 |
|
复合分支程序
例题:
从键盘输入一位十六进制数,并将其转换为十进制数输出显示。
算法分析:
从键盘输入一个十六进制数,有以下四种情况:
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 |
|
多分支程序
分支向量表:
如果在分支结构中有超过两个以上的多个可供选择的分支,这就是多分支结构。
如果对多分支的条件逐个查询以确定是哪一个分支,只会增加代码和时间,为了尽快进入某个分支,可以采用分支向量表法。
例题:
根据键盘输入的一位数字(1~4),使程序转移到4个不同的分支中去,以显示键盘输入的数字。
算法分析:从键盘输入一个数1~4,
1. 建立一个分支向量表branch ,集中存放四个分支的偏移地址;
2. 每个偏移地址位16位,占用2个单元;
3. 四个分支的偏移地址在转移地址表的地址是:转移地址表首址+输入数字(0~3)×2;
4. 用间接寻址方式转向对应分支。
1 |
|
循环计数程序
例题:
把BX中的二进制数用十六进制显示.(设BX=123AH)
1 |
|
条件循环程序
在循环程序中,有时候每次循环所做的操作可能不同,即循环体中有分支的情况,需要依据某一个标志来决定做何操作。标志位为1表示要做操作A,标志位为0表示要做操作B,我们可把这种标志字称为逻辑尺。
例题:
从键盘输入8位二进制数作为逻辑尺。再输入一个英文字母,根据逻辑尺当前的最高位标志显示输出该字母的相邻字符,标志位为0则显示其前趋字符,标志位为1则显示其后继字符。显示相邻字符后,逻辑尺循环左移一位,再接收下一个字母的输入,并依据逻辑尺显示相邻字符,直到回车键结束程序。
新转跳符:JNC
1 |
|
条件计数循环程序
例题:
设置键盘缓冲区为16个字节,从键盘输入一串字符,然后再从键盘输入一个单个字符,查找这个字符是否在字符串中出现,如果找到,显示该字符串,否则显示‘NOT FOUND’。
1 |
|
Ch8-子程序设计
子程序结构
①子程序调用指令
子程序定义:在模块化程序设计中,经常把程序中某些具有独立功能的部分编写成独立的程序模块,称为子程序。
主程序通过CALL指令调用子程序。
子程序执行完毕后通过RET指令回到主程序。
CALL调用指令:
格式: CALL DST
操作:首先把下一条指令的地址(返回地址)压入堆栈保存,再把子程序的入口地址置入IP(CS)寄存器,以便实现转移。
对于段内调用,只是向堆栈保存IP寄存器的值。(段内CS相同不需要记录)
对于段间调用,是先向堆栈保存CS寄存器的值,再向堆栈保存IP寄存器的值。
RET返回指令:
格式1: RET
格式2: RET EXP
操作:把堆栈里保存的返回地址送回IP(CS)寄存器,实现程序的返回。
对于段内调用,弹出一个字到IP寄存器。
对于段间调用,先弹出一个字到IP寄存器,再 弹出一个字到CS寄存器。
解析:这个程序就是A调用B,然后B在调用C,需要注意的是,A调用B是段间调用,所以要CALL FAR PTR B,而B调用C是段内调用,要CALL NEAR PTR C。段间调用要压栈保护CS和IP,而段内调用只需要压栈保护IP即可。
②过程定义与过程结构
过程定义伪指令
Procedure_Name PROC Attribute
┇
Procedure_Name ENDP
如:
main proc far /near
┇
main endp
NEAR为近,是段内调用。FAR类型为远,是跨段调用。
如调用程序和子程序在同一代码段,则使用NEAR属性;如调用程序和子程序不在同一代码段,则使用FAR属性。
主程序的过程定义属性应为FAR。,因为dos首先会调用主程序main,因此第一次一定是远调
基本定义结构如下:
1.子程序和调用程序在一个代码段
1 |
|
2.子程序和调用程序不在一个代码段
1 |
|
③ 保存和恢复现场寄存器
子程序调用 CALL:首先将返回地址压栈,然后把子程序的入口地址送入IP/CS寄存器。
子程序返回 RET:将堆栈里保存的返回地址送回IP/CS寄存器。
在子程序中对主程序的现场实施保护和恢复
在进入子程序后,对将要使用的寄存器,先保存这些寄存器的值,在子程序退出前恢复这些寄存器的值。
这里要注意 你压入的顺序和弹出的顺序是相反的。
子程序的参数传递
入口参数(调用参数):主程序传递给子程序。
出口参数(返回参数):子程序返回给主程序。
传递的参数:值传递和地址传递。
①用寄存器传递参数
用寄存器传递参数就是约定某些寄存器存放将要传递的参数。该方法简单,执行的速度也很快。但由于寄存器数量有限,不能用于传递很多的参数。
main中的三句话很重要,为的是可以正常返回,结束程序
main proc far
push ds
xor ax,ax
push ax
例题:
从键盘输入一个十进制数(小于65536的正数),显示输出该数的十六进制形式。通过寄存器传送变量。
1 |
|
②用变量传递参数
参数较多时可以用约定的变量在过程间传递参数。
例题:
键盘输入字符串到缓冲区后,对缓冲区内容降序排序并输出。
请仔细观察双循环的写法,十分重要十分重要十分重要十分重要十分重要
1 |
|
③用地址表传递参数
在主程序中建立一个地址表,把要传递的参数地址放在地址表中,然后把地址表的首地址放入寄存器,子程序通过寄存器间接寻址方式从地址表中取得所需参数,可以设计通用子程序处理其他类似字符串排序问题。
例题:
采用通过地址表传递参数地址的方法,键盘输入缓冲区并对其内容排序和输出。
1 |
|
④用堆栈传递参数
例题:
键盘输入缓冲区内容排序并输出,用堆栈传递参数地址。
提示:会考这三句设置ss和sp
1 |
|
1 |
|
⑤用结构变量传递参数
结构就是把逻辑上互相关联的一组数据以某种形式组合在一起。在程序中,若要多次使用相同的一组数据格式,那么我们就可以把这一组数据格式定义为一个结构数据。
结构类型的定义:结构名 STRUC
……
结构名 ENDS
STRUC伪指令只是定义了一种结构模式,还没有生成结构变量。
用结构预置语句生成结构变量并赋值。
结构预置语句格式:
变量 结构名 <各字段赋值>
对结构字段初值的修改,并非所有字段的初值都可以修改,只有简单结构字段和字符串字段初值才可以修改。简单结构字段是指由伪指令DB、DW或DD定义的单项变量。
1 |
|
1 |
|
多模块程序设计
汇编程序是可以由两个asm文件构成的。
1 |
|
1 |
|
各模块先分别汇编,然后再连接:
Link 812main+812sub
Link 的次序影响结果,主模块在前面。
Ch9-宏汇编
宏定义,宏调用,宏展开
宏是源程序中一段有独立功能的程序代码。它只需要在源程序中定义一次,就可以多次调用,调用时只需要用一个宏指令语句就可以了。
宏功能既可以实现程序复用,又能方便的传递多个参数。
宏定义:
宏指令名 MACRO [形参1,形参2,…]
<宏定义体>
ENDM
TIPS:
1.宏定义体是一组有独立功能的程序代码。
2.宏指令名给出宏定义的名称,调用时就使用宏指令名来调用宏定义。第一个符号必须是字符。
3.哑元表给出了宏定义中所用到的形式参数,每个哑元之间用逗号隔开。
例题:用宏指令实现两数的相加。
1 |
|
例题:用宏指令实现两个八位有符号数的乘法
1 |
|
例题:
某工厂工人的周工资由计时工资和计件工资组成,计时工资按每小时工资率RATE乘以工作小时数计算;计件工资按超定额部分乘以SUP计算(超定额=实际完成的工件数MADE-定额工件数PART),工资总额放在WAGE中。
1 |
|
宏定义的嵌套
这种嵌套结构的特点是外层宏定义的宏体中又有宏定义,只有调用外层宏定义一次后,才能调用内层宏指令。
例题:
用嵌套的宏定义实现两个八位数的算术运算。
1 |
|
宏定义中使用宏调用
宏定义中使用的宏调用必须已经定义。
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!