文章目录
一、MASM中的表达式
汇编程序可以识别的能够在指令和伪指令中出现的运算对象及运算符的组合。expression
有数值表达式和地址表达式两种,在汇编时得到一个值(数值或地址值)
1.1算术运算符
例: MOV AX,35 * 6
MOV AL,8+4-1
73 MOD 3=1
18H SHL 2=60H
1.2逻辑运算符
例:MOV AL,35 AND 15
MOV AL,11001100B OR11110000B
1.3关系运算符
- 参与关系运算的必须是两个数值;
- 关系不成立,结果为0000H(FALSE);
- 关系成立,结果为FFFFH(TRUE)。
如PORT_VAL=10
则PORT_VAL LT 5=0
1.4分析运算符(数值返回运算符)
用于分析存储器操作数的属性。
- OFFSET 返回其后变量或标号的偏移地址;
- SEG 返回其后变量或标号的段地址 ;
- TYPE 返回该变量的类型属性所表示的字节数 ;
- LENGTH 返回该变量所包含的单元数(字节、字或双字);
- SIZE 返回该变量所包含的总字节数。
SIZE=LENGTH*TYPE
1.5合成运算符(属性运算符)
1.5.1指针运算符PTR
格式: <类型>PTR <表达式>
功能:用于指定其后表达式的类型
- 类型可以是:BYTE ,WORD,DWORD 或NEAR,FAR
- 表达式是地址表达式:存储器数、LABEL或子程序名
- 属性运算符用来给指令中的操作数指定一个临时属性,而暂时忽略当前的属性。它作用于操作数时,则忽略了操作数当前的类型(字节或字)及属性(NEAR或FAR),而给出一个临时的类型或属性。
1.5.2指定运算符THIS
格式: THIS attribute 或 type
功能:指定一个变量具有BYTE、WORD、DWORD类型属性,指定一个标号具有FAR、NEAR属性。
1.5.3段超越ES:
格式: ES: <存储器寻址方式>
功能:用段超越前缀指明操作数的段寄存器名
1.5.4分离字节运算符HIGH
格式: HIGH <表达式>
功能:用于分离运算对象的高字节
表达式必须具有常量值,如常数、地址表达式。不能是存储器操作数或寄存器内容。
1.5.5分离字节运算符LOW
格式: LOW <表达式>
功能:用于分离运算对象的低字节
1.5.6运算符的优先级
1.6伪指令语句
- 由汇编程序执行的“指令系统”
- 用于定义变量、分配存储区、定义逻辑段、指示程序开始和结束等。
1.6.1数据定义伪指令
格式:[Variable] 助记符 Operad,…,Operand [ ;注释]
功能: 为变量名定义初值或预置内存空间。
(1)数据定义伪指令助记符:(Define byte)
- DB 定义的变量为BYTE型
- DW 定义的变量为Word
- DD 定义的变量为Double Word
- DQ 定义的变量为QuadroWord DT
定义的变量为TenByte 伪指令的性质决定所定义变量的属性;
定义字符串必须用DB伪指令
(2)重复操作符
为一个数据区的各单元设置相同的初值
格式:[变量名] 伪指令助记符 n DUP(初值,…)
DUP——Duplicate
?——随机数
1.6.2符号定义伪指令
(1)等值伪指令
格式:Expression_name EQU Expression
功能:用符号名取代后边的表达式,不可重新定义
EQU说明的表达式不占用内存空间,EQU伪指令给符号定义一个值。在程序中,凡是出现该符号的地方,汇编时均用其值代替。
(2)等号伪指令
格式:<符号名> = <表达式>
功能: 把表达式的值赋值给符号名。对所定义名字能多次重复定义,且以最后一次定义值为准。
1.6.3段定义伪指令
(1)ORG
格式:ORG 表达式
功能:指出段内程序代码或变量的起始偏移地址 。数值表达式的值为下条指令的偏移地址值,范围是0000H~FFFFH。
(2)段定义伪指令SEGMENT和ENDS
段定义伪指令可将源程序划分成若干段。通常,一个完整的汇编源程序由4个段组成,即: 数据段、扩展段、堆栈段和代码段。
格式:
<段名> SEGMENT[定位类型][组合类型][’类别’]
…
<段名> ENDS
说明:
- SEGMENT和ENDS应成对使用,缺一不可。
- 段名是给定义的段所起的名称,段名不可省略。
- 其他可选项是赋予段名的属性,可以省略。
a.其他可选项的定位类型:
- BYTE:字节边界, 即从任意边界开始
- WORD:字边界,本段起始地址可从偶地址处开始存放。
- PARA:节(Paragraph)的边界,16个字节为一个节。本段从能被16整除的地址处开始存放,即本段起始物理地址XXXX0H。(缺省)
- PAGE:页(Page)边界,256个字节为一页。本段从能被256整除的地址处开始存放,即本段起始物理地址XXX00H。
b.其他可选项的组合类型 (Combine-type)
指示汇编程序, 各逻辑段组合方式
- 该项缺省:则各逻辑段不组合
- PUBLIC: 将不同模块中具有相同段名的逻辑段连接成一个大逻辑段,连接次序由连接命令指定。
- STACK:与PUBLIC类似, 但仅限于堆栈段的组合
- COMMON:将各同名分段组合为一个段, 各同名分段有相同的起始地址,因此会发生重叠,COMMEN段的长度等于原来最大逻辑分段的长度。
- MEMORY:当几个逻辑段连接在一起时,指定本逻辑段定位在地址最高的地方。若连接时有几个指定MEMORY的段, 则汇编程序只将首先遇到的段作为MEMORY段, 其余的段则作为COMMON段。
- AT 表达式:表示本逻辑段段地址是表达式所计算出来的结果。它不能用来指定代码段。
c.‘类别’ (‘class’)
- 类别必须放在单引号‘ ’之内
- 其作用是在连接时决定各逻辑段的装入顺序
- 当几个程序模块进行连接时,具有相同类别名的逻辑段按连接出现的先后顺序被装入连续的内存区
- 没有类别名的逻辑段, 与其他无名逻辑段一起连续装入内存。
(3)段分配伪指令ASSUME
- 伪指令ASSUME用于通知汇编程序,各个段和段寄存器的关系。
- 以便对使用变量或标号的指令汇编出正确的目的代码(关键是段地址)
- 代码段里面的第一条。
格式:ASSUME assignment, …, assignment
其中assignment说明分配情况。其格式为:Segment register name : segment name
1.6.4过程定义伪指令PROC
PROC
ENDP
NEAR / FAR
格式:
<过程名> PROC [ NEAR] / [FAR ]
┇
RET
<过程名> ENDP
功能:用于定义一个过程体
1.6.5程序结束伪指令END
END
END [表达式]
格式:END [标号]
功能:告诉汇编程序源程序到此结束。其中标号是程序中第一条可执行语句前的标号。
二、程序设计
设计步骤:
- 分析实际问题,抽象描述问题的模型
- 确定算法
- 画程序流程图
- 分配内存工作单元和寄存器
- 编写程序
- 上机调试
判断程序质量的标准:
- 程序的正确性
- 程序的可读性
- 程序的执行时间
- 程序所占内存大小
一般原则:
- 自顶向下的层次结构设计
- 自底向上的程序编制
- 结构化程序设计(顺序、分支和循环)
三种结构的特点是只有一个入口和一个出口,没有永远执行不到的死语句,没有死循环。
2.1 顺序程序设计
顺序程序结构是一种最简单的程序结构。在流程图中,处理框一个接一个执行,既无分支,也无循环和转移。是一种简单的程序结构。
主要考虑的问题:
如何选择指令、内存空间如何分配、选择怎样的算法、如何选择存储单元和寄存器。
在设计程序时应尽量考虑使用寄存器(因CPU对寄存器操作数读取的速度快于对存储器操作数读取的速度)。
EX1. 从BUFF开始的内存单元中,存放着两个字型数据X、Y,编程求X+Y。并将相加的和(假设仍为一个字数据)存放于内存RESULT存储单元中。
分析:这是一个典型的顺序程序设计,需要一个数据段和一个代码段即可。
代码如下:
DATA SEGMENT
BUFF DW 1234H,5678H
RESULT DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START: MOV AX , DATA
MOV DS , AX
;-----------------------------------------------------
ENTR: MOV AX , BUFF
ADD AX , BUFF+2
MOV RESULT , AX
MOV AX , 4C00H
INT 21H
CODE ENDS
END START
加载结果:
有关字符、数码转换的处理
- 计算机处理字符时,常用的字符编码是ASCII 码。
- 数字和字母的ASCII码是一个有序序列
数字0~9 : 30H ~ 39H
大写字母A~Z : 41H ~ 5AH
小写字母a~z : 61H ~ 7AH
2.2 分支程序设计
(1)分支设计
分支程序结构要求程序在运行过程中需要根据不同的情况或条件作出判断,并转向相应的处理程序。分支是通过条件转移指令实现的。
总结:
1、必须先影响状态标志位,再使用条件转移。CMP AL , 0
2、一个条件语句,两分支。n个条件语句,最多n+1个分支。
3、每个分支末尾,必须用JMP跳转到出口。最后一个分支可省。
(2)多分支
可依次测试条件是否满足,若满足条件则转入相应分支入口,若不满足继续向下测试,直到全部测试完。简单,直观,速度慢。
(3)跳转表实现多分支
利用跳转表实现多分支,可以直接找到相应入口,利用该法需建立一个跳转表,表中含每个分支的入口地址。
a.根据表内地址分支
跳转表中存放了每个分支程序的入口地址,只要找到表地址,再将其内容取出,即可得到每个分支程序的入口地址。
表地址=跳转表首址+偏移地址
b.根据表内指令分支
跳转表中存放着转移指令,查表后程序执行转移指令将转到相应的子程序去。
c.根据关键字分支
跳转表中存放关键字及相应分支地址。
2.3 循环程序设计
循环初始化:循环程序工作单元的初始化赋值。
循环体:重复执行的一段程序。
循环工作部分: 循环程序的核心。
循环工作调整:重复执行的环境调整。
循环结束判断:每一次循环要有二个出口。根据循环工作调整的情况,判断是否满足结束条件。若满足结束条件,退出循环;若不满足结束条件,继续循环。
循环程序的两种结构形式:
EX2. 从BUFF开始的内存单元中,存放着10个字节型带符号数,编程求其最大值,并将结果存放于内存MAX存储单元中。
分析:这是一个循环次数已知的循环程序设计,需要一个数据段和一个代码段即可。
算法: 将第一个数放到AL中,并与之后的9个数进行逐一比较,并保持较大数在AL中。比较9次,就得到最大值。
程序代码:
DATA SEGMENT
BUFF DB 01H,11H,22H,33H,44H
DB 55H,66H,77H,88H,99H
MAX DD ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START: MOV AX , DATA
MOV DS , AX
LEA SI , BUFF
MOV AL , [SI]
INC SI
MOV CX , 09H
AGAIN: CMP AL , [SI]
JGE NEXT
MOV AL , [SI]
NEXT: INC SI
LOOP AGAIN
MOV MAX , AX
MOV AX , 4C00H
INT 21H
CODE ENDS
END START
加载结果:
3+1结构:(初始化+循环体+循环控制)+善后处理
1、初始化:为循环做准备工作。只执行一次。
包括:建立指针,设置循环次数,设置其他变量的初值。
2、循环体:是循环的主体,完成循环的操作。执行N次。
包括:循环工作部分,修改循环指针。
3、循环控制:判断循环是否结束。执行N次。
包括:循环条件修改,判断循环结束否。
4、善后处理:存储运算结果,执行1次。有的程序可以没有这部分。
包括:存结果,参数传递。
EX3. 从STRINGA单元开始有一个字符串,以’$'作为结束标志。求字符串的长度。并将结果存放于内存COUNT存储单元中。
分析: 这是一个循环次数未知的循环程序设计,也就是DO WHILE结构。
算法: 用SI作数据指针,用CX作计数器。取出一个数到AL中,并与结束标志比较,是美元符就结束,不是则将CX加一。
程序如下:
DATA SEGMENT
STRINGA DB ‘ABCDEFGHIJ123KL$’
COUNT DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START: MOV AX , DATA
MOV DS , AX
LEA SI , STRINGA
XOR CX,CX ;清零CX
LP: MOV AL , [SI]
CMP AL, ‘$’
JE DONE
INC CX
INC SI
JMP LP
DONE: MOV COUNT , CX
MOV AX , 4C00H
INT 21H
CODE ENDS
END START
加载结果:
2.4 子程序设计
子程序是编程中常用的重要方法,它只需编写一次,测试一次而可以多次重复使用。把这些相同的计算机操作编成一个子程序(过程PROC)。使用CALL或RET指令调用或返回。
EX4. 把16位二进制数转换为5位十进制数的BCD码。
分析: 16位二进制无符号数,最大是65535。因此转换成十进制数最多5位。
算法: 将该16位二进制数除以10,余数就是个位数;同理,再将商除以10,余数是十位数;然后,可以分别得到百位数、千位数、万位数。
解:假设主程序在调用该子程序时,已将需要转换的16位数送入AX中。且将转换完的结果的首地址送入BX。
代码如下:
BTOBCD PROC
PUSH AX
PUSH CX
PUSH DX
PUSH SI
;---------------------------------------------
MOV SI , 5
MOV CX , 10
AGAIN: XOR DX , DX
DIV CX
ADD DL ,30H
DEC SI
MOV [BX][SI] ,DL
OR SI , SI
JNZ AGAIN
;------------------------------------------
POP SI
POP DX
POP CX
POP AX
RET
BTOBCD ENDP
加载结果:
子程序需要注意的问题:
1、保护现场和恢复现场
为避免出错,在进入子程序后应首先把子程序中用到的寄存器内容保存到堆栈中。在退出子程序前再将其恢复。
保护和恢复的对象:在子程序中需要使用的内部寄存器。
(1)一定要保护:子程序中使用的寄存器;在返回后主程序需继续使用的寄存器。
(2)不用保护:作为子程序的结果传送给主程序的寄存器。
2、子程序与主程序的参数传递
(1)寄存器参数传递:适合于参数较少的情况,传递速度较快。
(2)存储单元参数传递:适合于参数较多的情况。
(3) 堆栈参数传递:适合于参数较多的情况,在子程序嵌套与递归调用的情况下使用,不容易出错。
3、子程序说明文件
子程序为功能独立的程序段,而且会为主程序多次调用。因此为方便使用,在编写并调试好子程序后,应该及时给子程序编写相应的说明文件,其内容应该包含下列5个部分:
(1)子程序名
(2)子程序所完成的功能
(3)子程序用到的寄存器和存储单元
(4)入口参数及其传递方式
(5)出口参数及其传递方式
4、子程序嵌套
子程序调用其他子程序,称子程序嵌套,子程序可多重嵌套调用。
递归调用:子程序调用子程序本身。
2.5 上机与实践
2.5.1DOS操作系统下:
编辑、修改和运行汇编语言程序,需要用文本编辑软件、宏汇编程序、连接程序和调试程序。
- 文本编辑:EDIT UltraEdit
- 汇编程序: MASM TASM
- 连接程序:LINK TLINK
- 调试程序:DEBUG TD CV
2.5.2WINDOWS环境下:
- Visual Studio
- MASMPLUS
- MASM for WINDOWS
- MASM32
- RedASM
如果学 16位的汇编,虚拟机要装dos + masm 6.11 。
如果学 32位的汇编,虚拟机装winxp+ masmplus 是比较好的 。
win7 64位,不可以直接运行,因为64位os不兼容。
用汇编语言编写的程序不能由机器直接执行。
要使用 汇编程序 和 连接程序。
- 源程序 ASM
- 目标程序 OBJ
- 可执行程序 EXE
2.6功能调用
2.6.1DOS 功能调用
高级调用,操作系统提供。包含多个子功能的功能包,用软中断指令调用,中断类型码固定为21H;各子功能采用功能号来区分。
调用的主要功能:
- 设备管理
- 目录管理
- 文件管理
- 其它
调用的步骤:
(1)MOV AH,功能号
(2) <置相应参数>
(3)INT 21H
1)从键盘输入单字符并显示
格式:MOV AH,01H
INT 21H ;输入的字符在AL中
2)从键盘输入但不显示
格式:MOV AH,08H
INT 21H ;输入的字符在AL中
3)屏幕显示单字符
格式:MOV AH,02H ;功能号O2H
MOV DL,待显字符的ASCII码
INT 21H
4)屏幕显示字符串
格式:LEA DX,待显字符串偏移地址
MOV AH,09H
INT 21H
功能:将当前数据区中以 ‘$’结尾的字符串送显示器显示,被显示的字符串必须以结束标志结束,且所显示的内容不应出现非可见的ASCII码。
5)打印输出
格式:MOV AH,05H
MOV DL,待输出字符的ASCII码
INT 21H
功能:将DL中的字符送打印接口,打印输出。
6)从键盘输入字符串
格式:LEA DX,缓冲区首偏移地址
MOV AH,0AH
INT 21H
功能:从键盘上往指定缓冲区中输入字符串并送显示器显示。
7)返回DOS操作系统
格式:MOV AH,4CH
INT 21H
功能:终止当前程序,返回DOS
2.6.2 BIOS 功能调用
BIOS(Basic Input/Output System)固化在主机的ROM中,含有I/O设备的基本驱动程序,以中断服务程序的形势存在,处于DOS调用和硬件环境之间,和DOS功能调用相比,其优点是效率高,缺点是编程相对复杂。
在这里用的虚拟PC是emu8086,这个软件对初学汇编的人较友好
编程的时候依旧要小心谨慎,汇编虽然在语法上易于C等高级语言
但若不注意细节,依旧会让你run不了…