基本控制结构
字符与字符串的输入/输出方法
DOS系统调用INT 21H提供了字符及字符串的I/O功能,例如:
01H:从键盘读入一个字符
02H:显示一个字符
09H:显示一个字符串
0AH:从键盘读入一个字符串
字符与字符串的输入/输出方法
DOS系统调用INT 21H提供了字符及字符串的I/O功能,例如:
01H:从键盘读入一个字符
02H:显示一个字符
09H:显示一个字符串
0AH:从键盘读入一个字符串
1. 从键盘读入一个字符
功能号:01H
出口参数:AL = 输入字符的ASCII码
功能:等待从键盘读入一个字符,将其ASCII码送入AL,同时将该字符显示在屏幕上。
调用方法:
mov ah, 1
int 21h
说明:输入一个字符后,不需要回车。若只键入回车,则AL = 0DH。
2. 显示一个字符
功能号:02H
入口参数:DL = 要显示字符的ASCII码
功能:在当前光标位置显示DL中的字符,光标右移。
调用方法示例:
mov dl, 'A' ; 显示字符'A'
mov ah, 2
int 21h
说明:显示字符0DH和0AH将产生回车和换行的作用。
3. 显示一个字符串
功能号:09H
入口参数:DS:DX = 欲显示字符串在内存的首地址,且字符串必须以'$'(24H)作为结束符。
功能:在当前光标位置,显示由DS:DX所指的、以'$'结尾的字符串,且光标右移。其中,'$'不算在显示的字符串之内。
调用方法示例:
String db 'Hello!', '$' ; 显示字符串'Hello!'
mov ax, seg String
mov ds, ax
mov dx, offset String
mov ah, 9
int 21h
4. 从键盘读入一个字符串
功能号:0AH
入口参数:DS:DX = 输入缓冲区首地址
输入缓冲区格式:第0字节事先设置为缓冲区最多能容纳的字符个数(包括回车);第1字节将存放实际字符个数(不包括回车),由系统自动设置;从第2字节开始存放实际输入的字符串,最后为回车符。
功能:从键盘读入一个字符串,存入DS:DX所指的缓冲区。
调用方法示例:
buf db 10, ?, 10 dup (?) ; 定义输入缓冲区
mov ax, seg buf
mov ds, ax
lea dx, buf
mov ah, 0ah
int 21h
5. 字符与字符串的输入/输出举例
DOS系统调用只提供了字符与字符串的输入/输出方法。如果要输入/输出其它类型的数据,如常用的整数,则必须由应用程序来实现与字符之间的转换。例如,欲输出整数126,只能以字符(串)的方式输出'1' 、 '2' 、 '6' 。
对于程序员来说,在需要用户输入时给出输入提示,对输入数据进行有效性检查,在输出结果时说明其表示的含义,是一种好的设计习惯。
功能号:01H
出口参数:AL = 输入字符的ASCII码
功能:等待从键盘读入一个字符,将其ASCII码送入AL,同时将该字符显示在屏幕上。
调用方法:
mov ah, 1
int 21h
说明:输入一个字符后,不需要回车。若只键入回车,则AL = 0DH。
2. 显示一个字符
功能号:02H
入口参数:DL = 要显示字符的ASCII码
功能:在当前光标位置显示DL中的字符,光标右移。
调用方法示例:
mov dl, 'A' ; 显示字符'A'
mov ah, 2
int 21h
说明:显示字符0DH和0AH将产生回车和换行的作用。
3. 显示一个字符串
功能号:09H
入口参数:DS:DX = 欲显示字符串在内存的首地址,且字符串必须以'$'(24H)作为结束符。
功能:在当前光标位置,显示由DS:DX所指的、以'$'结尾的字符串,且光标右移。其中,'$'不算在显示的字符串之内。
调用方法示例:
String db 'Hello!', '$' ; 显示字符串'Hello!'
mov ax, seg String
mov ds, ax
mov dx, offset String
mov ah, 9
int 21h
4. 从键盘读入一个字符串
功能号:0AH
入口参数:DS:DX = 输入缓冲区首地址
输入缓冲区格式:第0字节事先设置为缓冲区最多能容纳的字符个数(包括回车);第1字节将存放实际字符个数(不包括回车),由系统自动设置;从第2字节开始存放实际输入的字符串,最后为回车符。
功能:从键盘读入一个字符串,存入DS:DX所指的缓冲区。
调用方法示例:
buf db 10, ?, 10 dup (?) ; 定义输入缓冲区
mov ax, seg buf
mov ds, ax
lea dx, buf
mov ah, 0ah
int 21h
5. 字符与字符串的输入/输出举例
DOS系统调用只提供了字符与字符串的输入/输出方法。如果要输入/输出其它类型的数据,如常用的整数,则必须由应用程序来实现与字符之间的转换。例如,欲输出整数126,只能以字符(串)的方式输出'1' 、 '2' 、 '6' 。
对于程序员来说,在需要用户输入时给出输入提示,对输入数据进行有效性检查,在输出结果时说明其表示的含义,是一种好的设计习惯。
JMP指令的灵活运用
用JMP指令实现转移的多种方法:
使用标号, 近转移
使用标号, 远转移
以通用寄存器表示目标偏移地址
以内存变量表示目标偏移地址
以内存变量表示目标的32位分段地址
用JMP指令实现转移的多种方法:
使用标号, 近转移
使用标号, 远转移
以通用寄存器表示目标偏移地址
以内存变量表示目标偏移地址
以内存变量表示目标的32位分段地址
双分支结构
双分支结构相当于高级语言的IF-THEN-ELSE形式。IF-THEN结构只是IF-THEN-ELSE形式的特例,即ELSE部分为空。
通常,使用条件转移指令Jcc与无条件转移指令JMP来实现分支。一般必须先安排比较或算术、逻辑运算等影响标志位的指令,然后用Jcc指令判断条件,以实现分支转移。
对应于IF-THEN-ELSE结构的80x86汇编语言代码形如:
<为测试条件cc做准备>
Jcc ElseCode
<THEN 程序段>
jmp EndOfIF
ElseCode:
<ELSE 程序段>
EndOfIF:
注意,程序隐含是顺序执行的,在THEN分支体执行后,不会自动跳过ELSE分支体,而是继续执行其后的代码。
对应于IF-THEN结构的80x86汇编语言代码形如:
<为测试条件cc做准备>
Jcc EndOfIF
<THEN 程序段>
EndOfIF:
双分支结构相当于高级语言的IF-THEN-ELSE形式。IF-THEN结构只是IF-THEN-ELSE形式的特例,即ELSE部分为空。
通常,使用条件转移指令Jcc与无条件转移指令JMP来实现分支。一般必须先安排比较或算术、逻辑运算等影响标志位的指令,然后用Jcc指令判断条件,以实现分支转移。
对应于IF-THEN-ELSE结构的80x86汇编语言代码形如:
<为测试条件cc做准备>
Jcc ElseCode
<THEN 程序段>
jmp EndOfIF
ElseCode:
<ELSE 程序段>
EndOfIF:
注意,程序隐含是顺序执行的,在THEN分支体执行后,不会自动跳过ELSE分支体,而是继续执行其后的代码。
对应于IF-THEN结构的80x86汇编语言代码形如:
<为测试条件cc做准备>
Jcc EndOfIF
<THEN 程序段>
EndOfIF:
多分支结构
多分支结构相当于高级语言的CASE语句。
多分支程序的设计方法主要有:
逻辑分解方法
将多分支结构以逻辑等效的方法,分解为一串双分支结构。
地址表方法
在数据段定义一个地址表,依次存放各分支对应处理程序的入口地址。
通过将相应处理程序入口地址取入某寄存器,用间接转移指令实现转移。
转移表方法
在代码段建立一个转移表,依次存放实现各分支的转移指令。
通过跳到转移表的相应位置执行其中的转移指令,从而实现转到相应分支的处理程序。
循环结构
循环结构的基本形式
循环结构的两种基本形式:
WHILE结构:先判断、后执行
其特点是进入循环后,先判断循环控制条件。若满足循环结束条件,则退出循环;否则,继续执行循环体。循环体可能一次也不执行。
UNTIL结构:先执行、后判断
其特点是进入循环后,先执行循环体,然后判断循环控制条件。若满足循环结束条件,则退出循环;否则,继续执行循环体。循环体至少执行一次。该结构相当于PASCAL语言的REPEAT-UNTIL结构或C语言的DO-WHILE结构。
具体选择哪一种结构,取决于问题的特性以及使用者的偏好。一般来说,WHILE结构可能用得多一些。
此外,象高级语言的FOR循环结构,主要用在循环次数已知的情况下。FOR结构总可以转换为等价的WHILE或UNTIL结构。
循环程序的控制方法
(1)计数控制
适用于循环次数已知的情况,包括正计数与倒计数两种方法。
(2)条件控制
在实际问题中,常常有循环次数未知的情况。此时,就必须通过特定条件来控制循环。
(3)开关控制
有时,循环内部又有分支,且每次循环执行的分支具有一定规律,这种结构非常类似于多分支结构。此时,可以采用开关来控制循环。具体方法是:
在进入循环前,预置第一次循环的开关走向;在每次循环结束前,设置下一个开关方向,以便执行相应的操作。
在实际应用中,开关的形式多种多样。例如,设置一个状态变量,取值0、1和2,分别表示执行的不同操作,就是一个开关。
(4)逻辑尺控制
如果循环内分支的规律性不强,开关控制方法就难以胜任了。一种较有效的方法就是逻辑尺控制。所谓逻辑尺,就是一个位串,用1位或多位来控制每次循环所执行的操作。
(1)计数控制
适用于循环次数已知的情况,包括正计数与倒计数两种方法。
(2)条件控制
在实际问题中,常常有循环次数未知的情况。此时,就必须通过特定条件来控制循环。
(3)开关控制
有时,循环内部又有分支,且每次循环执行的分支具有一定规律,这种结构非常类似于多分支结构。此时,可以采用开关来控制循环。具体方法是:
在进入循环前,预置第一次循环的开关走向;在每次循环结束前,设置下一个开关方向,以便执行相应的操作。
在实际应用中,开关的形式多种多样。例如,设置一个状态变量,取值0、1和2,分别表示执行的不同操作,就是一个开关。
(4)逻辑尺控制
如果循环内分支的规律性不强,开关控制方法就难以胜任了。一种较有效的方法就是逻辑尺控制。所谓逻辑尺,就是一个位串,用1位或多位来控制每次循环所执行的操作。
串操作
串操作指令及其用途
1. 串操作指令
(1) MOVS(Move String):串传送
语法格式:
MOVSB
MOVSW
MOVSD
功能描述:
ES:[DI] = DS:[SI];
if ( DF = 0 ) then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,MOVS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)复制到ES:DI所指的内存单元。然后,SI和DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
串操作指令及其用途
1. 串操作指令
(1) MOVS(Move String):串传送
语法格式:
MOVSB
MOVSW
MOVSD
功能描述:
ES:[DI] = DS:[SI];
if ( DF = 0 ) then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,MOVS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)复制到ES:DI所指的内存单元。然后,SI和DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
(2)LODS(Load String):串装入
语法格式:
LODSB
LODSW
LODSD
功能描述:
AL/AX/EAX = DS:[SI];
if ( DF = 0 )then
SI = SI + size;
else
SI = SI – size;
endif
其中, size = 1(B)、2(W)或4(D)。
也就是说,LODS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)复制到AL/AX/EAX。然后,SI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
(3)STOS(Store String):串存储
语法格式:
STOSB
STOSW
STOSD
功能描述:
ES:[DI] = AL/AX/EAX;
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,STOS(B/W/D)将AL/AX/EAX的值复制到ES:DI所指的内存单元。然后,DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
语法格式:
LODSB
LODSW
LODSD
功能描述:
AL/AX/EAX = DS:[SI];
if ( DF = 0 )then
SI = SI + size;
else
SI = SI – size;
endif
其中, size = 1(B)、2(W)或4(D)。
也就是说,LODS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)复制到AL/AX/EAX。然后,SI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
(3)STOS(Store String):串存储
语法格式:
STOSB
STOSW
STOSD
功能描述:
ES:[DI] = AL/AX/EAX;
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,STOS(B/W/D)将AL/AX/EAX的值复制到ES:DI所指的内存单元。然后,DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:无。
(4)CMPS(Compare Strings):串比较
语法格式:
CMPSB
CMPSW
CMPSD
功能描述:
DS:[SI] – ES:[DI];
if ( DF = 0 )then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,CMPS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)与ES:DI所指目的串的1个元素(字节/字/双字)进行比较,根据比较结果设置标志位。然后,SI和DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:同CMP。
语法格式:
CMPSB
CMPSW
CMPSD
功能描述:
DS:[SI] – ES:[DI];
if ( DF = 0 )then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,CMPS(B/W/D)将DS:SI所指源串的1个元素(字节/字/双字)与ES:DI所指目的串的1个元素(字节/字/双字)进行比较,根据比较结果设置标志位。然后,SI和DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
对标志位的影响:同CMP。
(5)SCAS(Scan String):串扫描
语法格式:
SCASB
SCASW
SCASD
功能描述:
AL/AX/EAX – ES:[DI];
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,SCAS(B/W/D)将AL/AX/EAX与ES:DI所指目的串的1个字节/字/双字进行比较,根据比较结果设置标志位。然后,DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
(6)重复前缀
REP ; 当CX <> 0时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在MOVS、LODS或STOS(B/W/D)之前
REPZ/REPE ; 当CX <> 0且ZF = 1时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
REPNZ/REPNE ; 当CX <> 0且ZF = 0时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
说明:
若CX初值为0,则不执行任何操作,而且标志位不变。
语法格式:
SCASB
SCASW
SCASD
功能描述:
AL/AX/EAX – ES:[DI];
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是说,SCAS(B/W/D)将AL/AX/EAX与ES:DI所指目的串的1个字节/字/双字进行比较,根据比较结果设置标志位。然后,DI增加或减少1/2/4。若DF = 0,则增加,否则减少。
(6)重复前缀
REP ; 当CX <> 0时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在MOVS、LODS或STOS(B/W/D)之前
REPZ/REPE ; 当CX <> 0且ZF = 1时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
REPNZ/REPNE ; 当CX <> 0且ZF = 0时,重复执行后面的串指令
; 每执行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
说明:
若CX初值为0,则不执行任何操作,而且标志位不变。
2. 串指令的共性
源串:地址由DS:SI表示。
目的串:地址由ES:DI表示。
自动修改地址:每次数据操作后,SI和DI自动递增或递减,取决于操作的数据类型(字节、字或双字)以及DF的值。若DF = 0, 则SI和DI自动增加1、2或4。否则,减1、2或4。可以使用CLD或STD指令设置DF。
计数器:使用重复前缀时,由CX表示数据个数,每重复1次操作,CX减1。
源串:地址由DS:SI表示。
目的串:地址由ES:DI表示。
自动修改地址:每次数据操作后,SI和DI自动递增或递减,取决于操作的数据类型(字节、字或双字)以及DF的值。若DF = 0, 则SI和DI自动增加1、2或4。否则,减1、2或4。可以使用CLD或STD指令设置DF。
计数器:使用重复前缀时,由CX表示数据个数,每重复1次操作,CX减1。
3. 串指令的用途
串指令主要用于处理连续的内存单元,与重复前缀配合使用更有效。例如:
MOVS用于将一个内存块的数据复制到另一块;
CMPS用于比较两个内存区的数据;
SCAS可以在一个内存区中扫描与给定值首次匹配或不匹配的元素;
STOS能将一个内存区的所有单元初始化为给定值。
LODS一般不用重复前缀。
串指令主要用于处理连续的内存单元,与重复前缀配合使用更有效。例如:
MOVS用于将一个内存块的数据复制到另一块;
CMPS用于比较两个内存区的数据;
SCAS可以在一个内存区中扫描与给定值首次匹配或不匹配的元素;
STOS能将一个内存区的所有单元初始化为给定值。
LODS一般不用重复前缀。
字符串处理
1. ASCII字符串的表示方法
字符串是一种特殊的数据串,比其它类型的串更常使用,因此,有时将字符串简称为串。
ASCII字符串的表示方法主要有下列两种:
(1)长度前缀法。
由首字节指出字符串长度,常被称作Pascal串,为Pascal语言的大多数版本所支持。例如:
String1 db 6, 'String‘
(2)0终止法。
以0作为结束标志,常被称作C串,为C/C++语言所采用。例如:
String2 db ' String ', 0
1. ASCII字符串的表示方法
字符串是一种特殊的数据串,比其它类型的串更常使用,因此,有时将字符串简称为串。
ASCII字符串的表示方法主要有下列两种:
(1)长度前缀法。
由首字节指出字符串长度,常被称作Pascal串,为Pascal语言的大多数版本所支持。例如:
String1 db 6, 'String‘
(2)0终止法。
以0作为结束标志,常被称作C串,为C/C++语言所采用。例如:
String2 db ' String ', 0
2. 字符串处理举例
转载于:https://blog.51cto.com/zhangbikai/166689