文章目录
字符串操作
串操作指令简介
主机与外设之间的交互,大多采用字符串的形式,为了方便地实现字符串操作,简化程序设计,80x86提供了字符串操作指令
字符串操作指令共有5条:
- 传送指令 MOVS
- 搜索指令 SCAS
- 比较指令 CMPS
- 读取指令 LODS
- 存储指令 STOS
以上指令可使用的重复前缀如下:
- REP,无条件重复CX寄存器指定的次数
- REPE/REPZ,在CX≠0 && ZF=1时重复
- REPNE/REPNZ,在CX≠0 && ZF=0时重复
所有的字符串操作指令,使用相同的寄存器,标志位和符号
- 源串指示器:DS:SI/ESI
- 目的串指示器:ES:DI/EDI
- 重复次数计数器:CX/ECX
- SCAS指令的搜索值在:AL/AX/EAX中
- LODS指令的目的地址为:AL/AX/EAX
- STOS指令的源地址为:AL/AX/EAX
- 传送方向:
- DF = 0 自动增量
- DF = 1 自动减量
- _SB:不带操作数的字节串操作指令
- _SW:不带操作数的字串操作指令
- _SD:不带操作数的双字串操作指令
串操作指令
传送指令
MOVS OPD, OPS
MOVSB:字节传送
MOVSW:字传送
MOVSD:双字传送
功能:
- DS:[SI] / [ESI] --> ES:[DI] / [EDI]
- 自动修改指针,指向下一个位置
比较指令
CMPS OPD, OPS
CMPSB:字节比较
CMPSW:字比较
CMPSD:双字比较
功能:
- DS:[SI] / [ESI] – ES:[DI] / [EDI]
- 根据相减的结果,置标志位,源串与目的串内容不变
- 自动修改指针,指向下一个位置
- 后面一般跟转移指令
- 串比较指令,是源操作数减去目的操作数
搜索指令
SCAS OPD
SCASB:字节搜索
SCASW:字搜索
SCASD:双字搜索
功能:
-
字节操作:(AL) – (ES:[DI] / [EDI])
-
字 操 作:(AX) – (ES:[DI] / [EDI])
-
双字操作:(EAX) – (ES:[DI] / [EDI])
-
根据相减的结果置标志位,但不保存结果
-
自动修改指针,指向下一个位置
-
主要用来在字符串中搜索某个值
从源串中取数指令
LODS OPD
LODSB:字节串中取数
LODSW:字串中取数
LODSD:双字串中取数
功能:
-
字节操作:(DS:[DI] / [EDI]) -> (AL)
-
字 操 作:(DS:[DI] / [EDI]) -> (AX)
-
双字操作:(DS:[DI] / [EDI]) -> (EAX)
-
将字符串存储区的某个数据,送到累加寄存器中
-
自动修改指针,指向下一个位置
往目的串中存数指令
STOS OPD
STOSB:往字节串中存数
STOSW:往字串中存数
STOSD:往双字串中存数
功能:
-
字节操作: (AL) -> (DS:[DI] / [EDI])
-
字 操 作:(AX) -> (DS:[DI] / [EDI])
-
双字操作:(EAX) -> (DS:[DI] / [EDI])
-
将累加寄存器中的值,送到目的串
-
自动修改指针,指向下一个位置
宏功能程序设计
- 子程序的调用,需要额外的时间和空间花销
- 当重复部分较短时,一般设计成宏指令
- 宏汇编语言提供:宏指令的定义调用、重复汇编、条件汇编
- 宏指令使用三步骤:宏定义、宏调用、宏拓展
宏定义
宏定义使用伪指令 MACRO
和 ENDM
实现
定义格式
宏指令名 MACRO 形参
宏体
ENDM
- 宏指令名,类似于函数名
- 形参可以出现在宏体任何地方,形参以逗号隔开
- 宏体是要重复的部分,包括一系列机器指令和伪指令
- ENDM和MACRO成对出现
- 宏指令一定要放在被调用之前
宏调用
宏指令完成定义后,可以在源程序中调用
调用格式:宏指令名 实际参数
宏拓展
- 宏汇编程序在汇编期间,先扫描宏定义,将宏名字、形参、宏体填入宏定义表中
- 遇到宏调用时,实参替换形参
宏定义于宏调用中的参数
带间隔符的实参
有时,实参是一串带间隔符(空格、逗号)的字符串,为了不至于混淆,用尖括号 <> 将它们括起来,尖括号中的内容代表实参
例如用宏指令定义申请堆栈空间:
STACK0 <500 DUP(0)>
数字实参
- 有时希望用符号做实参,希望传进去的是符号的值,而不是符号本身,可以符号的前面加
%
- 特殊宏操作符
%
用来将其后的表达式转换为对应的值 - % 后面的符号,一定是直接用EQU或者=赋值的符号常量,或者汇编时能确定值的表达式,而不能是变量和寄存器名
宏参数的连接
- 在宏定义中,有些形参需要夹在字符串中,为了将这种形参标识出来,需要在这样的形参前面加符号
&
- 如果形参后面还跟有字符串,则还应在形参后面加符号
&
- 宏汇编程序识别后,将符号连成一个完整的字符串
宏体中的变量与标号
- 宏体中经常需要定义一些变量和标号,如果进行了多次宏拓展,则会出现变量或标号重复定义的错误
- 为解决这个问题,提供了伪指令LOCAL
LOCAL 形参
功能:
- 在宏拓展时,宏汇编程序自动为其后的形参顺序生成特殊符号,并用这些特殊符号取代宏体中的形参,从而避免了符号重复定义的错误
- LOCAL语句只能作为宏体的第一条语句,它后面的形参即为本宏定义中所定义的变量和标号
重复汇编
- 有时源程序中,会连续重复完全相同,或者几乎完全相同的一组语句
- 这时采用重复汇编方式比把他们定义成宏指令更能简化程序设计
给定次数
REPT 表达式
重复块
ENDM
功能:让宏汇编程序将重复块连续地汇编“表达式”所指定的次数
不定次数
次数由实参的个数确定,根据实参形式的不同,选择IRP和IRPC
IRP 形参, <实参1,实参2,...,实参n>
重复块
ENDM
- 让宏汇编程序将重复块重复汇编由实参个数所确定的次数,并在每次重复时,依次用相应位置的实参代替形参
- 实参必须用尖括号括起来,并且各实参之间用逗号隔开
IRPC 形参,字符串
重复块
ENDM
- 将重复块重复汇编,重复的次数由字符串中的个数决定,并在每次重复时,依次用相应位置的字符代替形参
- 字符串不带引号
条件汇编
- 条件汇编是指,允许宏汇编程序根据条件,决定某一段程序是否参加汇编
- 使用条件汇编伪指令时,条件表达式各项的值必须在第一遍扫描中求得,否则表达式不正确
使用格式:
IF ... 条件表达式
条件汇编块1
ELSE
条件汇编块2
ENDIF
正条件 | 反条件 | 表达式的形式 | 检测条件 |
---|---|---|---|
IF | IFE | 数值表达式 | 表达式值不为0(为0)为真 |
IF1 | IF2 | 第一次/第二次扫描 | |
IFDEF | IFNDEF | 符号 | 符号已被(未被)定义或被(未被)说明为外部符号为真 |
IFB | IFNB | <参数> | 该参数对应的实参存在(不存在)为真 |
IFIDN | IFDIF | <参数1>, <参数2> | 字符串参数1与字符串参数2相等(不相等)为真 |
宏库的使用
宏库的建立
- 对于经常使用的宏定义,用户可以将他们集中在一起,建成宏库供用户随时调用
- 宏库为文本文件,文件名任意指定
- 利用TYPE命令可以查看文本的内容
宏库的使用
- 程序中需要调用宏库时,首先将宏库加入到自己的源文件中,然后按照宏库中各宏定义的规定调用即可
- 伪指令 INCLUDE 用于将宏库加入源文件一起进行汇编
INCLUDE 文本文件名
功能:将文件内容加入汇编,全部汇编完后,再汇编后面的内容
条件汇编与宏定义的使用
- 条件汇编伪指令常与宏定义一起使用
- 可以使用条件汇编,使宏库只在第一遍扫描时添加,这样加快第二次扫描,避免列出长长的汇编列表清单
- 对于宏库中用不上的宏指令,可以使用伪指令PURGE取消
IF1
INCLUDE MACRO.LIB
ENDIF
PURGE WRITE
宏指令与子程序的比较
- 处理的时间不同:宏指令是在汇编期间,由宏汇编程序处理的;而子程序调用是在目标程序执行期间,由CPU直接执行的
- 处理的方式不同:宏指令必须先定义后使用,宏调用用宏体置换宏指令名,实参置换形参,而子程序的调用不发生这种代码和参数的置换,而是CPU将控制方向由主程序转向子程序
- 目标程序的长度不同:由于每次宏调用都要进行宏拓展,因而使用宏指令不会缩短目标程序,而子程序通过CALL指令调用,无论执行多少次,目标代码只出现一次,因此,目标程序短,占用空间小
- 执行速度不同:执行子程序需要使用堆栈保护和恢复现场,因而执行速度慢,而宏指令不存在这些问题
- 参数传递方式不同:宏调用可实现参数的代换,参数形式不受限制,而子程序参数一般是地址或者操作数
模块化程序设计
- 在汇编语言中,一个以 END 语句为结束标志的源程序,称为一个代码块
- 它经过汇编源程序汇编后,生成一个目标文件,也称为目标模块
- 连接程序将多个目标模块连接在一起时,需要两个信息,一是模块间通信方式,二是段之间的组合方式
- 通信方式是指:一个模块可访问另外模块中所定义的标号、变量、符号常量的方式
- 组合方式是指:多个模块以什么方式组合在一起
组合方式
定位方式
定位方式是对该段起始地址所提出的要求,即告诉连接程序,在各个段装配在一起时,前一段放完后,后面一个段将从一个什么样的起始边界开始存放
定位方式有4种选择:
- PARA:节:表示该段要从能被16整除的地址开始存放,即最低4位必须为0
- WORD:字:表示该段要从偶数地址开始存放,即最低1位必须为0
- BYTE:字节:表示对该段存放的首地址不作要求,任何地址处都可以开始存放
- PAGE:页:该段要从能被256整除的地址开始存放,即最低8位必须为0
其中PARA为默认方式,不指定时为此方式
组合方式
组合方式的作用是向连接程序提供本段同其他段的组合关系,可以选择以下几种关系
- 不选择:表示本段与其他段逻辑上不发生关联
- PUBLIC:表示应将本段与其他模块中的同名、同 ‘类别’ 段按模块连接的顺序相邻的连接在一起,组成一个物理段,该段大小不能超过64KB
- STACK:与PUBLIC功能相同,但该方式仅对堆栈段
- COMMON:表示本段和同名、同 ‘类别’ 的其他段应该具有相同的段首址,即将本段与这些段相覆盖,覆盖长度取决于最长的COMMON段
- AT表达式:表示将本段装在根据表达式计算出的段地址上,但AT方式不能包含代码和初始化数据,不能用于用户代码段,仅用于访问系统数据,表示已在内存中的数据和数据地址
- MEMORY:表示将本段定位在所有连接在一起的段之上(最高地址上),如果几个段都选择MEMORY组合方式而又同时连接在一起,那么只有宏汇编程序遇到的第一段处理为MEMORY,其它作为COMMON段
类别
- 段的 ‘类别’ 是用单括号括起来的字符串来表示的
- 连接时,‘类别’ 相同的所有段,按先后顺序存放在连续的存储区,且每段都有自己的起始地址
通信方式
公共符号与外部符号
- 公共符号:不仅可被定义自己的模块访问,还可以供其它模块访问的符号,用PUBLIC说明
- 外部符号:在该模块内部访问,而不在该模块内定义的符号,用EXTERN说明
PUBLIC 符号
- 用来说明其后的符号是公共符号,可以被其它模块所引用
- 这些符号必须是在本模块中定义的符号常量、标号、变量,各名字之间用逗号隔开,一个模块中的同一个名字仅可被PUBLIC说明一次
- 一般放在程序的开头
EXTERN 符号:类型
- 用来说明本模块中需要引用的、由其他模块所定义的符号
- 这些符号必须在所定义的模块中声明为PUBLIC
- 类型为:ABS(符号常量)、BYTE、WORD、DWORD、NEAR、FAR
连接程序的功能
连接程序可以将若干个目标模块连接在一起,连接程序主要有以下功能:
- 将指定的若干个目标模块和子程序库中的子程序模块连接在一起,解决各模块中段的组合、定位问题,确定目标模块中浮动地址及公共符号的引用问题,生成一个可再定位的装入模块,即能够执行的文件(.EXE)
- 产生一个地址分配文件(.MAP)
源程序综合举例
模块程序设计中的注意事项
模块划分
- 如果一个程序段被很多模块公用,则他应该该是一个独立的模块
- 如果若干程序段处理的数据是公用的,则这些程序段应该放在一个模块
- 若两个程序段的利用率差别很大,则应该分属于两个模块
- 一个模块不能太大,也不能太小,过大则通用性差,过小则时间和空间浪费
- 力求使模块具有通用性,通用性越强模块的利用率越高
- 各模块在功能、逻辑上相互独立,避免使用转移语句在模块间转来转去
- 各模块之间接口简单,尽量减少公共符号的个数,尽量不公用数据存储单元,在结构或编排上有联系的数据应该放在一个模块中,以免相互影响
- 每个模块的结构应该尽量设计成单入口、单出口的形式
程序文件命名
- 标识出程序设计者
- 标识出模块功能
- 标识出模块之间的连接关系
标号的定义
- 模块出处
- 功能名称
- 分支代号
变量和缓冲区的定义
- 缓冲器或变量标志:用B为缓冲器名开头,用V为变量名开头,用S为字符串开头
- 第二个字母可表示定义的类型
模块注释
为方便子模块的调用,在子模块前面应该有详细的说明,包括:
- 子模块的功能
- 入口参数
- 出口参数
- 所用到的主要变量