汇编程序设计及高级汇编语言技术.docx
《汇编程序设计及高级汇编语言技术.docx》由会员分享,可在线阅读,更多相关《汇编程序设计及高级汇编语言技术.docx(17页珍藏版)》请在冰豆网上搜索。
汇编程序设计及高级汇编语言技术
第6章 汇编程序设计及高级汇编语言技术
6.1顺序程序设计
编制一个汇编语言程序的步骤如下:
(1)分析题意确定算法。
(2)根据算法画出程序框图。
这一点对初学者特别重要,这样做可以减少出错的可能性。
画图时可以从粗到细把算法逐步地具体化。
(3)根据框图编写程序。
(4)上机调试程序。
。
程序有顺序、循环、分支和子程序四种结构形式。
下面分别进行介绍。
顺序程序设计,又叫直接程序设计。
它是相对于分支程序和循环程序设计而言的。
因此,可以说顺序程序是既不包含分支,又不包含循环的程序,顺序程序是从第一条指令开始,按其自然顺序,一条指令一条指令地执行,在运行期间,CPU既不跳过某些指令,也不重复执行某些指令。
一直执行到最后一条指令为止,此程序的任务也就完成了,前面我们所举的例子,大多数是这种程序。
汇编语言中的大部分指令,如数据传送指令、算术运算指令、处理器控制指令、移位指令和逻辑运算指令,都可以用来构造顺序结构。
例6.1.1:
从键盘键入0至9中任一自然数X,求其立方值。
求一个数的立方值可以利用乘法和查表方法来实现,在本例中利用查表方法来实现。
构造一个立方表,事先将0至9的立方存放在表中,求0至9的立方值可直接从表中查出。
表存储单元分配:
字节变量x存放键入的自然数x,字变量xxx中存放x的立方值。
从表结构可知,x的立方值在表中的存放地址与x有如下对应关系:
(TAB+2*x)=x的立方值
源程序如下:
STACKSEGMENTSTACK
DB200DUP(0)
STACKENDS
DATASEGMENT
INPUTDB"PLEASEINPUTX(0....9):
$"
TABDW0,1,8,27,64,125,216,343,512,729
XDB?
XXXDW?
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA,SS:
STACK
BEGIN:
MOVAX,DATA
MOVDS,AX
MOVAH,9
LEADX,INPUT
INT21H
MOVAH,1
INT21H
ANDAL,0FH
MOVX,AL
ADDAL,AL
MOVBL,AL
MOVBH,0
MOVAX,TAB[BX]
MOVXXX,AX
MOVAH,4CH
INT21H
CODEENDS
ENDBEGIN
6.2.1分支程序设计概述
顺序程序设计是最基本的程序设计技术,但实际上在很多情况下,不单要求计算某个确定的计算公式,而且要求根据变量变化的情况,从几个公式中选择一个进行计算,这时,对变量所处的状态要进行判断,根据判断结果决定程序的流向,这就是分支程序设计技术。
分支程序结构可以有两种形式。
它们分别相当于高级语言中的IF—THEN—ELSE语句和CASE语句,它们适用于要根据不同条件作不同处理的情况。
IF—THEN—ELSE语句可以引出两个分支,CASE语句则可以引出多个分支,不论哪一种形式,它们的共同特点是:
运行方向是向前的,在某一种确定条件下,只能执行多个分支中的一个分支。
在8086/8088的程序中,指令执行的顺序由代码寄存器CS和指令指针IP的内容确定。
CS寄存器用来存放当前代码段的基址,当前代码段是从基址开始以后连续的一个64K字节的存储空间。
IP的内容作为偏移地址。
一个存储单元的偏移地址是指从段的基址算起到这个单元所处位置的字节数。
CS和IP结合起来给出了下条指令在存储器中的位置。
CPU根据IP和CS的值,从存储器中取出一条指令,送到CPU中去执行,然后根据程序转移以后的地址逐条读取指令重新填入指令列队。
程序的分支一般用转移指令来产生。
6.2.2分支程序设计
分支结构程序设计的关键在于准确地知道操作结果影响的标志位状态和正确地使用条件转移指令。
根据对条件的判断而选择不同的处理方法是人的基本智能体现。
计算机根据对标志位的判断而决定程序流向的条件转移指令表明计算机能实现这种智能。
当我们运用条件转移指令去解决具体问题时,能否达到预期目的,主要取决于编程人员的思维是否符合逻辑,以及能否正确使用相应的条件转移指令。
在这一节里我们通过具体的例子来说明分支程序的设计。
例6.2.1:
设内存中有三个互不相等的无符号字数据,分别放在ARG开始的字单元,编制程序将其中最大值存入MAX单元。
分析:
求三个无符号数中的最大值,只要把三个数据两两比较,用JA/JNB/JNA/JB/JC等指令就可判断两数的大小,从而选出其中最大值。
源程序如下:
SSEGSEGMENTSTACK
STKDB20DUP(0)
SSEGENDS
DSEGSEGMENT
ARGDW7138H,84A6H,29EH
MAXDW?
DESGENDS
CSEGSEGMENT
ASSUMECS:
CSEG,DS:
DSEG,SS:
SSEG
FMAX:
MOVAX,DSEG
MOVDS,AX
MOVAX,SSEG
MOVSS,AX
MOVSP,SIZESTK
LEASI,ARG
MOVAX,[SI]
MOVBX,[SI+2]
CMPAX,BX
JAEFMAX1
MOVAX,BX
FMAX1:
CMPAX,[SI+4]
JAEFMAX2
MOVAX,[SI+4]
FMAX2:
MOVMAX,AX
MOVAH,4CH
INT21H
CSEGENDS
ENDFMAX
6.3.1循环程序设计概述
有时我们会需要能按一定规律,多次重复执行的一串语句,这类程序叫循环程序。
我们在这里将向大家介绍循环程序的结果和控制方法以及循环程序的设计方法。
循环程序一般由四个部分组成:
(1)置循环初值部分:
这是为了保证循环程序能正常进行循环操作而必须做的准备工作。
循环初值分两类:
一类是循环工作部分的初值,另一类是控制循环结束条件的初值。
(2)工作部分:
即需要重复执行的程序段。
这是循环的中心,称之为循环体。
(3)修改部分:
按一定规律修改操作数地址及控制变量,以便每次执行循环体时得到新的数据。
(4)控制部分:
用来保证循环程序按规定的次数或特定条件正常循环。
例6.3.1:
已知道有n个元素存放在以BUF为首址的字节存储区中,试统计其中负元素的个数。
显然,每个元素为一个8位有符号二进制数。
统计其中负元素个数的工作可用循环程序实现。
存储单元及寄存器分配如下:
BX:
BUF存储区的地址指针,初值为BUF的偏移地址,每循环一次之后,其值增1。
CX:
循环计数器,初值为BUF区中元素的个数n,每循环一次之后,其值减1。
AX:
用来记录负元素的个数,初值为零。
字变量R:
用来存放负元素的个数。
统计负元素个数流程图的循环结构类同于上图6-2中的图(a)。
在控制部分之后,用(AX)-->R将负元素个数送入了字变量R之中。
源程序如下:
STACK SEGMENTSTACK
DB200DUP(0)
STACK ENDS
DATASEGMENT
BUF DB–2,5,-3,6,100
DB0,-20,-9,8,-110,20
N =$-BUF
R DW?
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA,SS:
STACK
BEGIN:
MOVAX,DATA
MOVDS,AX
LEABX,BUF
MOVCX,N
MOVAX,0
LOPA:
CMP[BX],BYTEPTR0
JGENEXT
INCAX
NEXT:
INCBX
DECCX
JNELOPA
MOVR,AX
MOVAH,4CH
INT21H
CODE ENDS
ENDBEGIN
该程序的循环体被重复执行了n次,即当(CX)=N,N-1,…,1时循环执行,当(CX)=0时结束循环,将负元素个数送入字变量R中之后,返回DOS状态。
程序执行后,R中是负号元素个数5。
注意:
在循环程序中,置初值部分总是处于四个部分的首位。
在向循环体或控制部分转移时,千万不要转到置初值部分,否则会出现死循环。
例如,若不小心将例题中的标号LOPA上移两行,即移到"MOVCX,N"处,将导致系统死循环。
6.3.2循环程序设计
1.循环的控制方法
如何控制循环程序设计中一个重要环节。
常见的两种控制方法:
计数控制和条件控制。
(1)计数控制
当循环次数已知时,通常使用计数控制法。
假设循环次数为n,常常用以下三种方法实现计数控制和条件控制。
1)先将循环次数n送入循环计数器中,然后,每循环一次,计数器减1,直至循环计数器中的内容为0时结束循环。
如:
MOVCX,n
…;循环初值部分
LOOPA:
…;工作部分
…;修改部分
DECCX;控制部分
JNZLOOPA
其中工作部分、修改部分被重复执行n次,即当(CX)=n,n-1,…,1时,重复执行循环体,当(CX)=0时,结束循环。
2)先将循环次数的负值送入循环计数器中,然后每循环一次,计数器加1,直至计数器中的内容为零时结束循环。
例如:
MOVCX,-n
…;置循环初值部分
L0OPA:
…;工作部分
…;修改部分
INCCX;控制部分
JNZLOOPA
其中工作部分、修改部分被重复执行n次,即当(CX)=-n,-(n-1),…,-1时重复执行,当(CX)=0时结束循环。
3)先将0送入循环计数器中,然后没循环一次,计数器加1,直到循环计数器的内容与循环次数n相等时退出循环。
例如:
MOVCX,0
…;置循环初值部分
LOOPA:
…;工作部分
…;修改部分
INCCX;修改部分
CMPCX,n
JNELOOPA
其中,工作部分、修改部分重复执行n次,即当(CX)=0,1,…,n-1时重复执行,当(CX)=n时结束循环。
(2)条件控制
有些情况下,循环次数事先无法确定,但它与问题的某些条件有关。
这些条件可以通过指令来测试。
若测试比较的结果表明满足循环条件,则继续循环,否则结束循环。
例6.3.2:
统计AX寄存器中1的个数,并将结果存放在CL寄存器中。
…
MOVCL,0
L:
ANDAX,AX
JZEXIT;(AX)=0时,结束循环转EXIT
SALAX,1;将AX中的最高位移入CF中
JNCL;如果CF=0,转L
JNCL;如果CF=1,则(CL)+1àCL
JMPL;转L处继续循环
EXIT:
…
2.单重循环程序设计
所谓单重循环,即其循环体内不再包含循环结构。
下面分循环次数已知和未知两种情况讨论其程序设计方法
(1)循环次数已知的循环程序设计
对于循环次数已知的情况,通常采用计数控制方法来实现循环。
例6.3.3:
已知以BUF为首址的字存储区中存放着n个有符号二进制数,试编写程序,将其中大于等于0的数依次送入以BUF1为首址的字存储区中,小于0的数依次送入以BUF2为首址的存储区中。
分析:
实现以上功能的算法为:
将BUF区中的n个数逐次取出,判断其值是否大于等于0,若是,则送该数到BUF1区,否则送该数到BUF2区,如此重复,直至n个数处理完毕。
由此可见,实现以上算法的程序应该是一循环程序,其循环次数为n。
寄存器分配如下:
BX:
BUF存储区地址指针,初值指向BUF
SI:
正数存储区地址指针,初值指向BUF1
DI:
负数存储区地址指针,初指指向BUF2
CX:
循环计数器,初值为BUF区中数据个数n
AX:
用来暂时存放判断正负属性的数
源程序如下:
STACKSEGMENTSTACK
DB200DUP(0)
STACKENDS
DATASEGMENT
BUFDW–5,100,32767,-32768
DW–1,15,-4,-9,200,0,300
N=($-BUF)/2
BUF1DWNDUP(?
)
BUF2DWNDUP(?
)
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA,SS:
STACK
BEGIN:
MOVAX,DATA
MOVDS,AX
LEABX,BUF
LEASI,BUF1
LEADI,BUF2
MOVCX,N
LOPA:
MOVAX,[BX]
CMPAX,0
JGEL1
MOV[DI],AX
ADDDI,2
JMPNEXT
L1:
MOV[SI],AX
ADDSI,2
NEXT:
ADDBX,2
DECCX
JNELOPA
MOVAH,4CH
INT21H
CODEENDS
ENDBEGIN
(2)最大循环次数未知的循环程序设计
对于循环次数未知的情况,常用条件来控制循环。
3.多重循环程序设计
多重循环即循环体内套有循环。
设计多重循环程序时,可以从外层循环到内层循环一层一层地进行。
通常在设计外层循环时,仅把内层循环看成一个处理粗框,然后再将该粗框细化,分成置初值、工作、修改和控制四个组成部分。
当内层循环设计完之后,用其替换外层循环体中被视为一个处理粗框的对应部分,这样就构成了一个多重循环。
对于程序,这种替换是必要的,对于流程图,如果关系复杂,可以不替换,只要把细化的流程图与其对应的处理粗框联系起来即可。
例6.3.4:
在以BUF为首址的字节存储区中存放有n个无符号数x1,x2,…,xn,现需将它们按从小到大的顺序排列在BUF存储区中,试编写其程序。
分析:
对外这个问题的处理可采用逐一比较法,其算法如下:
将第一个问题存储单元中的数与其后n-1个存储单元中的数逐一比较,每次比较之后,总是把小者放在第一个存储单元之中,经过n-1次比较之后,n个数中最小直存入了第一个存储单元之中;接着将第二个存储单元中的数与其后的n-2个存储单元中的数逐一比较,每次比较之后,总是吧小者放在第二个存储单元之中,经过n-2次比较之后,n个数中第二小数存入了第二个存储单元之中;…;如此重复下去,当最后两个存储单元之中的数比较完之后,从小到大的顺序就实现了。
寄存器分配如下:
SI:
用来存放i的值,初值为1,每循环一次之后,其值增1。
DI:
用来存放j的值,初值为i+1,每循环一次之后,其值增1。
AL:
用来存放Xi。
源程序如下:
STACKSEGMENTSTACK
DB200DUP(0)
STACKENDS
DATASEGMENT
BUFDB30H,10H,40H,20H,50H,
DB70H,60H,90H,80H,0,0FFH
N=$-BUF
DATAENDS
CODESEGMENT
ASSUMENCS:
CODE,DS:
DATA,SS:
STACK
BEGIN:
MOVAX,DATA
MOVDS,AX
MOVSI,1
LOPI:
MOVDI,SI
INCDI
MOVAL,[BUF+SI-1]
LOPJ:
CMPAL,[BUF+DI-1]
JBENEXT
XCHG[BUF+DI-1],AL
MOV[BUF+SI-1],AL
NEXT:
INCDI
CMPDI,N
JBELOPJ
INCSI
CMPSI,N-1
JBELOPI
MOVAH,4CH
INT21H
CODEENDS
ENDBEGIN
次程序的运行结果如下:
00H,10H,20H,30H,40H,50H,60H,70H,80H,90H,0FFH
6.4.1子程序设计概述
在我们编写解决实际问题的程序时,往往会遇到多处使用相同功能的程序段,使用该程序段的唯一差别是对程序变量的赋值不同。
如果每次用到这个功能就重现书写一遍,那么不仅书写麻烦,容易出错,编辑,汇编它时,也会花费较多的时间,同时由于冗长,占用内存多。
如果把多次使用的功能程序编制为一个独立的程序段,每当用到这个功能时,就将控制转向它,完成后再返回到原来的程序,这就回大大减少编程的工作量。
这种可以被其它程序使用的程序段,我们就叫它子程序。
1.子程序定义伪指令
子程序定义伪指令有两条:
PROC和ENDP
使用这两条子程序定义伪指令所定义的子程序的一般格式如下:
PNPROC[NEAR]/[FAR] ;说明过程开始
…;过程体
PNENDP;说明过程结束
说明:
1)两条伪指令一前一后,如同过程体的语句括号,说明过程的开始与结束,中间为过程体。
这两条伪指令必须成对出现;
2)PN为过程名,用以标识不同的过程。
过程名的命名原则与标号相同,一般在程序设计风格上有所讲究。
给过程命名时最好用一个使人容易理解的过程名;
3)过程体即为一段独立的程序,是完成子程序功能的程序主体。
除了不能使用HLT等引起停机的指令而必须使用返回指令作为逻辑上最后一条指令之外,与以前所讲的程序设计没有任何区别。
也就是说,过程体中可以使用除引起停机的指令以外的任何指令;
4)NEAR和FAR分别指出所定义的过程是近过程还是远过程。
近过程只允许段内调用,即在转返过程中保持当前CS不变,只允许本段内程序使用。
远过程允许段间调用,即在转返过程中CS的值也发生变化,允许其他段中的程序使用。
NEAR和
FAR都不写时默认所定义的过程为近过程。
例6.4.1:
在代码段SEG1段定义两个过程SUB1和SUB2,并使SUB1只为本段程序调用,SUB2可为其他代码段程序调用。
程序的过程定义部分如下所示:
SEG1SEGMENTPUBLIC
…
SUB1PROCNEAR
…
SUB1ENDP
SUB2PROCFAR
…
SUB2ENDP
SEG1ENDS
2.调用指令CALL
3.返回指令RET
子程序的调用指令CALL和返回指令RET的讲解见第四章的有关章节。
6.4.2子程序程序设计
1.子程序的嵌套
我们已经知道,一个子程序也可以作为调用程序去调用另一个子程序,这种情况就称为子程序的嵌套。
嵌套的层次不限,其层数称为嵌套深度。
嵌套子程序的设计并没有什么特殊要求,除子程序的调用和返回应正确使用CALL和RET指令外,要注意寄存器的保存和恢复,以避免各层子程序之间发生因寄存器冲突而出错的情况。
如果程序中使用了堆栈,如使用堆栈来传送参数等,则对堆栈的操作要格外小心,避免因堆栈使用中的问题而造成子程序不能正确返回的出错情况。
例6.4.2:
十六进制到十进制的转换程序(通过寄存器传送变量)
HEXIDECSEGMENT
MAINPROCFAR
ASSUMECS:
HEXIDEC
START:
PUSHDS
SUBAX,AX
PUSHAX
REPEAT:
CALLHEXIBIN
CALLCRLF
CALLBINIDEC
CALLCRLF
JMPREPEAT
RET
MAINENDP
HEXIBINPROCNEAR
MOVBX,0
NEWCHAR:
MOVAH,1
INT21H
SUBAL,30H
JLEXIT
CMPAL,10D
JLADD_TO
SUBAL,27H
CMPAL,0AH
JLEXIT
CMPAL,10H
JGEEXIT
ADD_TO:
MOVCL,4
SHLBX,CL
MOVAH,0
ADDBX,AX
JMPNEWCHAR
EXIT:
RET
HEXIBINENDP
BINIDECPROCNEAR
MOVCX,10000D
CALLDEC_DIV
MOVCX,1000D
CALLDEC_DIV
MOVCX,100D
CALLDEC_DIV
MOVCX,10D
CALLDEC_DIV
MOVCX,1D
CALLDEC_DIV
RET
BINIDECENDP
DEC_DIVPROCNEAR
MOVAX,BX
MOVDX,0
DIVCX
MOVBX,DX
MOVDL,AL
ADDDL,30H
MOVAH,2
INT21H
RET
DEC_DIVENDP
CRLFPROCNEAR
MOVDL,0AH
MOVAH,2
INT21H
MOVDL,0DH
MOVAH,2
INT21H
RET
CRLFENDP
HEXIDECENDS
ENDSTART
6.5.1宏的定义
宏是源程序中一段有独立功能的程序代码。
它只需要在源程序中定义一次,就可以多次调用它,调用时只需要用一个宏指令语句就可以了。
宏定义是用一组伪操作来实现的。
其格式是:
macronameMACRO[dummyparameterlist]
…
ENDM
其中MACRO和ENDM是一对伪操作。
这对伪操作之间是宏定义休——是一组有独立功能的程序代码。
宏指令名(macro
name)给出该宏定义的名称,调用时就使用宏指令名来调用该宏定义。
宏指令名的第一个符号必须是字母,其后可以跟字母、数字或下划线字符。
其中哑元表(dummy
parameterlist)给出了宏定义中所用到的形式参数(或称虚参),每个哑元之间用逗号隔开。
6.5.2宏的调用和展开
经宏定义定义后的宏指令就可以在源程序中调用。
这种对宏指令的调用称为宏调用,宏调用的格式是:
macro_name[actualparameterlist]
实元表(actualparameterlist)中的每一项为实元,相互之间用逗号隔开。
当源程序被汇编时,汇编程序将对每个宏调用作宏展开。
宏展开就是用宏定义体取代源程序中的宏指令名,而且用实元取代宏定义中的哑元。
在取代时,实元和哑元是一一对应的。
即第一个实元取代第一个哑元,第二个实元取代第二个哑元…依此类推。
一般说来,实元的个数应该和哑元的个数相等,但汇编程序并不要求它们必须相等。
若实元个数大于哑元个数,则多余的实元不予考虑,若实元个数小于哑元个数,则多