汇编语言及编程实例(电子教案)

第四章 汇编语言程序设计

回顾:8086的内部结构、寄存器功能和工作过程,指令格式、寻址方式和功能。

本讲重点:了解汇编的概念及其方法, 掌握汇编程序的基本格式,常用运算符的使用方法,汇编的步骤。

4.1汇编语言的基本元素

一、汇编语言的语句格式

由汇编语言编写的源程序是由许多语句(也可称为汇编指令)组成的。每个语句由1~4个部分组成,其格式是:

[标号] 指令助记符 [操作数] [;注解]

其中用方括号括起来的部分,可以有也可以没有。每部分之间用空格(至少一个)分开,一行最多可有132个字符。

1. 标识符 给指令或某一存储单元地址所起的名字。可由下列字符组成:

字母:A ~ z ; 数字:0 ~ 9 ; 特殊字符:?、· 、@、一、$ 。

数字不能作标识符的第一个字符,而圆点仅能用作第一个字符。标识符最长为31个字符。当标识符后跟冒号时,表示是标号。它代表该行指令的起始地址;当标识符后不带冒号时,表示变量;伪指令前的标识符不加冒号。

2. 指令助记符 表示不同操作的指令,可以是8086的指令助记符,也可以是伪指令。

3. 操作数

例如: RET ;无操作数

COUNT: INC CX ;一个操作数

如果是伪指令,则可能有多个操作数,例如:

COST DB 3,4,5,6,7 ;5个操作数

MOV AX,[BP+4] ;第二个操作数为表达式

4. 注解

该项可有可无,是为源程序所加的注解,用于提高程序的可读性。

二、汇编语言的运算符

1. 算术运算符、逻辑运算符和关系运算符

算术运算符可以应用于数字操作数,结果也是数字。而应用于存储器操作数时,只有+、- 运算符有意义。

2. 取值运算符SEG、OFFSET、TYPE、SIZE和LENGTH

例如,定义: SLOT DW 25

则: MOV AX,SLOT;从SLOT地址中取一个字送入AX

MOV AX,SEG SLOT;将SLOT所在段的段地址送入AX

MOV AX,OFFSET SLOT;将SLOT所在段的段内偏移地址送AX

· 值如表4-1所示。

DB/DW/DD等定义的操作数) 例如:若 MULT-WORD DW 50DUP(0)

则 LENGTH(MULT-WORD)=50

SIZE(MULT-WORD)=100

注意:SIZE(X)=(LENGTH X)* (TYPE X)

3. 属性运算符

常用的有:

(1) 合成运算符PTR

它作用于操作数时,则忽略了操作数当前的类型(字节或字)及属性(NEAR或FAR),而给出一个临时的类型或属性, 一般格式:类型 PTR 表达式 功能:建立一个存储器地址操作数,它与其后的存储器地址操作数有相同的段地址偏移量,但有不同的类型。

例如:SLOT DW 25

此时SLOT已定义成字单元。若我们想取出它的第一个字节内容,则可用PTR对其作用,表4-1存储器操作数的类型属性及返回值

使它暂时改变为字节单元,即 MOV AL,BYTE PTR SLOT

三、表达式

由运算符和操作数组成的序列,在汇编时产生一个确定的值。这个值可以仅表示一个常量,也可以表示一个存储单元的偏移地址,相应的表达式称为常量表达式和地址表达式。

1. 常数

汇编语言语句中出现的常数可以有7种:

① 二进制数 后跟字母B,如01000001B。

② 八进制数 后跟字母Q或O,如202Q或202O。

③ 十进制数 后跟D或不跟字母,如85D或85。

④ 十六进制数 后跟H,如56H,0FFH。注意,当数字的第一个字符是A~F时,在字符前应添加一个数字0,以示和变量的区别。

另有,十进制浮点数、十六进制实数、字符和字符串

2. 常量操作数

可以为数字常量操作数或字符串常量操作数。前者可采用二进制、八进制、十进制或十六进制等计数形式;而后者则为相应字符的ASCII码。

3. 存储器操作数

存储器操作数是一个地址操作数,代表一个存储单元的地址,通常以标识符的形式出现。 存储器操作数可以分为变量及标号两种类型,如果存储器操作数所代表的是某个数据在数据段、附加段或堆栈段中的地址,那么这个存储器操作数就称为变量;如果存储器操作数所代表的是某条指令代码在代码段中的地址,那么这个存储器操作数称为标号。变量所对应的存储单元内容在程序的运行过程中是可以改变的,标号通常作为转移指令或调用指令的目标操作数,在程序运行过程中不能改变。

(1) 段地址:即存储器操作数所对应的存储单元所在段的段地址;

(2) 偏移地址:即存储器操作数所对应的存储单元在所在段内的偏移地址;

(3) 类型:变量的类型是相应存储单元所存放的数据项的字节数;而标号的类型则反映了相应存储单元地址在作为转移或调用指令的目标操作数时的寻址方式,可有两种情况,即 NEAR和FAR。具体值可见表4-1

4. 常量表达式

由常量操作数及运算符构成,在汇编时产生一个常量。

如PORT、VAL+1、 OFFSET SUM、SEG SUM、TYPE CYCLE等。

5. 地址表达式 由存储器操作数与运算符构成,必须有明确的物理意义。

例如 SUM+2、CYCLE-5

表达式SUM+2、CYCLE-5的值仍然是一个存储器操作数,该存储器操作数的段地址与类型属性分别与存储器操作数SUM及CYCLE相同,但偏移地址分别比SUM及CYCLE大2或小5。表达式是在汇编时计算的,而变量单元的内容在程序的运行过程中可以改变。

四、汇编语言程序汇编步骤

汇编语言程序要能在机器上运行,还必需将汇编源程序汇编成可执行程序。为此必须完成以下几个步骤。

1. 编辑源程序

2. 调用宏汇编对源程序进行汇编

3. 对目标程序进行连接

4. 运行可执行程序并调试

【习题与思考】

1.下列语句在存储器中分别为变量分配多少字节空间?并画出存储空间的分配图。 VAR1 DB

VAR2

VAR3

VAR4 10,2 5DUP(?),0 ‘HOW ARE YOU?’,‘$’ -1,1,0 DW DB DD

2. 假定VAR1和VAR2为字变量,LAB为标号,试指出下列指令的错误之处。

(1)ADD

(3)JMP VAR1,VAR2 (2)SUB AL,VAR1 LAB[SI] (4)JNZ VAR1

3. 对于下面的符号定义,指出下列指令的错误。

A1 DB ? A2 DB 10 K1 EQU 1024

(1) MOV

(3)CMP

K1,AX A1,A2 (2)MOV A1,AX EQU 2048 (4)K1

4.2 伪指令

【回顾】汇编的概念及其方法, 掌握汇编程序的基本格式。8086/8088的指令格式。

【本讲重点】了解伪指令的功能,掌握定义数据、符号、段、过程等伪指令的使用方法,能编写格式正确的汇编程序。

4.2 伪指令

伪指令用来对汇编程序进行控制,对程序中的数据实现条件转移、列表、存储空间分配等处理,其格式和汇编指令一样,但一般不产生目的代码,即不直接命令CPU去执行什么操作。

一、定义数据伪指令

· DB:定义字节,即每个数据是1个字节。

· DW:定义字,即每个数据占1个字(2个字节)。

· DD:定义双字,即每个数据占2个字。低字部分在低地址,高字部分在高地址。 · DQ:定义4字长,即每个数据占4个字。

· DT:定义10个字节长,用于压缩式十进制数,

例如:DATA1 DB 5,6,8,100

DATA2 DW 7,287

TABLE DB ? ;表示在TABLE单元中存放的内容是随机的;

当一个定义的存储区内的每个单元要放臵同样的数据时,可用DUP操作符。 一般格式:COUNT DUP(?),COUNT 为重复的次数,‚( )‛中为要重复的数据。 如:BUFFER DB 100 DUP(0) ;表示以BUFFER为首地址的100个字节中存放00H数据

BUFFER1 DB 100 DUP(3,5,2DUP(10),35),24,‘NUM’)

想一想存储区的情况?

二、符号定义伪指令EQU、=、及PURGE

· EQU 伪指令给符号定义一个值。在程序中,凡是出现该符号的地方,汇编时均用其值代替,

如:TIMES EQU 50

DATA DB TIMES DUP(?)

上述两个语句实际等效于如下一条语句:

DATA DB 50 DUP(?)

· ‚=‛伪指令可给初始变量赋值。

如:COUNT=100 ;COUNT=100

TIME=50 ;TIME=50

· PURGE伪指令用于释放由EQU伪指令定义的变量,使这些变量可以被重新定义。 PURGE TIMES ; 释放TIMES变量

TIMES EQU 2 ; 重新定义

三、段定义伪指令SEGMENT和ENDS

一般来说,一个完整的汇编源程序由3个段组成,即堆栈段、数据段和代码段。段定义伪指令可将源程序划分成若干段,以便生成目的代码和连接时将各同名段进行组合。 :

段名 SEGMENT [定位类型] [组合类型] [类别]

段名 ENDS

SEGMENT和ENDS应成对使用,缺—不可。其中段名是不可省略的。其它是可选项,是赋予段名的属性,可以省略。

例如: DATA SEGMENT

DW 20DUP(?)

DATA ENDS

四、设定段寄存器伪指令ASSUME 一般格式: ASSUME 段寄存器:段名[,段寄存器:段名,……] 功能:通知汇编程序,哪一个段寄存器是该段的段寄存器,以便对使用变量或标号的指令汇

编出正确的目的代码。在段名中,CODE表示代码段,DATA表示数据段,STACK表示堆栈段。

由于ASSUME伪指令只指明某一个段地址应存于哪一个段寄存器中,并没有包含将段地址送入该寄存器的操作。因此要将真实段地址装入段寄存器还需用汇编指令来实现。这一步是不可缺少的。

例如,CODE SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK

MOV AX,DATA ;DATA段值送AX

MOV DS,AX ;AX内容送DS,DS才有实际段值

CODE ENDS

当程序运行时,由于DOS的装入程序负责把CS初始化成正确的代码段地址,SS初始化为正确的堆栈段地址,因此用户在程序中就不必设臵。但是,在装入程序中DS寄存器由于被用作其它用途,因此,在用户程序中必须用两条指令对DS进行初始化,以装入用户的数据段地址。当使用附加段时,也要用MOV指令给ES赋段地址。

五、定义过程的伪指令PROC和ENDP

在程序设计中,可将具有一定功能的程序段看成为一个过程(相当于一个子程序),它可以被别的程序调用。

一个过程由伪指令PROC和ENDP来定义,其格式为:

过程名 PROC [类型]

过程体

RET

过程名 ENDP

其中过程名是为过程所起的名称,不能省略,过程的类型由FAR(远过程,为段间调用)和NEAR(近过程,在本段内调用)来确定,如果缺省类型,则该过程就默认为近过程。ENDP表示过程结束。过程体内至少应有一条RET指令,以便返回被调用处。过程可以嵌套,也可以递归使用。

例如一个延时100ms的子程序,其过程可定义如下,

DELAY PROC

PUSH BX

PUSH CX

MOV BL,10

;延时10ms,改变BL和CX中的值,即可改变延时时间。

AGAIN: MOV CX,2801 ;

WAIT; LOOP WAIT

DEC BL

JNZ AGAIN

POP CX

POP BX

RET

DELAY ENDP

CALL DELAY ;调用该过程

远过程调用时被调用过程必定不在本段内。

例如,有两个程序段,其结构如下:

CODE1 SEGMENT

ASSUME CS:CODE1

FARPROC PROC FAR

RET

FARPROC ENDP

CODE1 ENDS

CODE2 SEGMENT

ASSUME CS:CODE2

CALL FARPROC

…..

CODE2 ENDS

CODE1 段中的FARPROC 过程被另一段CODE2调用,故为远过程。

六、宏指令

在汇编语言书写的源程序中,若有的程序段要多次使用,为了简化程序书写,该程序段可以用一条宏指令来代替,而汇编程序汇编到该宏指令时,仍会产生源程序所需的代码。 宏指令的一般格式为:宏指令名 MACRO [形式参量表]

宏体

ENDM

例如:SHIFT MACRO

MOV CL,4

SAL AL,Cl

ENDM

这样定义以后,凡是要使AL中内容左移4位的操作都可用一条宏指令SHIFT来代替。

宏指令与子程序有许多类似之处。它们都是一段相对独立的、完成某种功能的、可供调用的程序模块,定义后可多次调用。但在形成目的代码时,子程序只形成一段目的代码,调

用时转来执行。而宏指令是将形成的目的代码插到主程序调用的地方。因此,前者占内存少,但执行速度稍慢;后者刚好相反。

七、ORG 伪指令

ORG伪指令规定了在某一段内,程序或数据代码存放的起始偏移地址。

: ORG

例如:DATA

BUFF1 SEGMENT DB 23,56H,‘EOF’ ORG 2000H BUFF2 DATA DB ‘STRING’ ENDS

上述变量定义中,BUFF1从DATA段偏移地址为0的单元开始存放,而BUFF2则从DATA段偏移为2000H的单元开始存放,两者不是连续存放。

八、汇编结束伪指令END

该伪指令表示源程序的结束.令汇编程序停止汇编。因此,任何一个完整的源程序均应有END指令。 一般格式: END [表达式]

其中表达式表示该汇编程序的启动地址。例如:

END START ;表明该程序的启动地址为START。

习题与思考:

1.数据定义语句如下所示:

FIRST DB 90H,5FH,6EH,69H

SECOND DB 5 DUP(?)

THIRD DB 5 DUP(?)

FORTH DB 5 DUP(?)

自FIRST单元开始存放的是一个四字节的十六进制数(低位字节在前),要求:

(1)编一段程序将这个数左移两位、右移两位后存放到自SECOND开始的单元(注意保留

移出部分。

(2)编一段程序将这个数求补以后存放到自FORTH开始的单元。

2.试编程序将内存从40000H到4BFFFH的每个单元中均写入55H,并再逐个单元读出比较,看写入的与读出的是否一致。若全对,则将AL臵7EH;只要有错,则将AL臵81H。

3.在当前数据段4000H开始的128个单元中存放一组数据,试编程序将它们顺序搬移到A000H开始的顺序128个单元中,并将两个数据块逐个单元进行此较;若有错将BL臵00H;全对则将BL臵FFH,试编程序。

3.3汇编程序设计

【回顾】8086的指令系统,汇编程序的基本格式,伪指令的功能,汇编程序的正确格式。

【本讲重点】掌握汇编程序的分析与设计方法(包括分支程序和循环程序)

一、简单程序设计

简单程序设计是没有分支,没有循环的直线运行程序,程序执行按照IP内容自动增加的顺序进行。

【例1】 利用查表法计算平方值。已知0 ~ 9的平方值连续存在以SQTAB开始的存储区域中,求SUR单元内容X的平方值,并放在DIS单元中。假定0≤X≤ 9且为整数。 分析:建立平方表,通过查表完成。

STACK SEGMENT

DB 100 DUP(?)

STACK ENDS

DATA SEGMENT

SUR DB ? DIS DB ? SQTAB DB 0,1,4,9,16,25,36,49,64,81 ; 0~9的平方表

DATA ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK,ES:DATA

BEGIN:PUSH DS

MOV AX,0 PUSH AX ;保证返回DOS, MOV AX,DATA

MOV DS,AX ;为DS送初值

LEA BX,SQTAB ;以下程序部分完成查表求平方值 MOV AH,0 ;亦可用查表指令完成(如下程序段) MOV AL,SUR ;AL=X LEA BX, SQTAB ADD

BX,AX ; MOV AL, SUR

MOV AL,[BX] ; XLAT MOV DIS,AL ; MOV DIS, AL

CODE ENDS

END BEGIN

【例2】已知Z=(X+Y)-(W+Z),其中X,Y,Z,W均为用压缩BCD码表示的数,写出程序。 分析:这也是一种典型的直线程序,在这里要注意是BCD数相加,要进行十进制调整。具

体程序如下:

MOV AL,Z MOV BL,W ADD DAA

MOV BL,AL ; BL=(W+Z) MOV AL,X MOV DL,Y ADD

AL,DL ; AL=(X+Y) AL,BL

DAA ;十进制调整 SUB

AL,BL ; AL=(X+Y)-(Z+W)

DAS ;十进制调整

MOV Z,AL ;结果送Z

二、分支程序设计

分支程序的基本思想是根据逻辑判断的结果来形成程序的分支,如图,若A成立,则执行P1;否则执行P2。

【例3】 试编写程序段,实现符号函数。

分析:变量X的符号函数可表示为:

1 X>0 Y= 0 X=0

-1 X

程序可通过对符号标志的判别来确定执行哪一分支。 START: MOV AX,BUFFER ;(BUFFER)=X

OR JE JNS

AX,AX

ZERO ;X=0,则转ZERO PLUS ;X为正数,则转PLUS

MOV BX,0FFFFH ;X为负数,则-1送BX JMP

CONT1

表4-2 子程序R1—R8的入口地址表 ZERO:

MOV BX,0

CONT1

JMP

PLUS: MOV BX,1

CONT1: ……

【例4】 利用表实现分支

根据AL中各位被臵位情况,控制转移到8个子程序P1~P8之一中去。转移表的结构如表4-2所示。

分析:对于这种程序关键要找出每种情况的转移地址,从图中可见

表地址=表基地址+偏移量, 而偏移量可由AL各位所在位臵*2求得。 DATA BASE

SEGMENT

DW SR0,SR1,SR2,SR3, SR4,SR5,SR6,SR7

DATA CODE

ENDS

SEGMENT

ASSUME CS:CODE,DS:DATA,ES:DATA BEGIN: PUSH DS

XOR AX,AX

PUSH AX

MOV AX,DATA MOV DS,AX LEA IN

BX,BASE ;表头送BX AL,PORT

GETBIT: RCR AL,1 ;右移一位

JC GETAD ;移出位是1? INC

BX

INC BX ;修改指针

JMP

GETBI

GETAD: JMP WORD PTR[BX] ;实现散转 CODE

ENDS

END BEGIN

根据跳转表构成方法不同,实现分支的方法也有所改变,下面有三个问题希望大家思考: (1) 若跳转表地址由段值和偏移量四个字节构成,程序应如何实现?

(2) 若跳转表中的内容由JMP OPRD指令构成,表的结构应如何组织、程序如何实现? (3) 上述程序若不用间接跳转指令,而改为直接跳转,程序如何变动? 【例5】 将内存中某一区域的原数据块传送到另一区域中。

分析 这种程序若源数据块与目的数据块之间地址没有重叠,则可直接用传送或串操作实现;若地址重叠,则要先判断源地址+数据块长度是否小于目的地址,若是,则可按增量方式进行,否则要修改指针指向数据块底部,采用减量方式传送。程序如下: DATA STR

SEGMENT

DB 1000DUP(?)

STR+7 STR+25

50

STR1 STR2

EQU EQU

STRCOUNT DATA STACK STAPN STACK

EQU

ENDS

SEGMENT PARA STACK ‘STACK’ DB ENDS

100DUP(?)

CODE

SEGMENT

ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK PROC PUSH DS SUB

AX,AX

GOO

PUSH AX MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX

MOV CX,STRCOUNT MOV SI,STR1 MOV DI,STR2 CLD PUSH SI ADD CMP POP JL STD ADD ADD REP RET ENDP ENDS END

GOO 图4-4 循环结构示意图 SI,STRCOUNT-1 DI,STRCOUNT-1

MOVSB SI,STRCOUNT-1 SI,DI OK

SI

OK:

GOO

CODE

三、循环程序设计

循环程序是经常遇到的程序结构,一个循环结构通常由以下几个部分组成。 1. 循环初始化部分 一般要进行地址指针、循环次数及某标志的设臵,相关寄存器的清零

等操作。只有正确地进行了初始化设臵, 循环程序才能正确运行,及时停止。

2. 循环体 是要求重复执行的程序段部分,对应于要求重复执行的操作。 3. 循环控制部分 由该部分修改并判断控制循环的条件是否满足。以决定是否继续循环。 4. 循环结束部分 如保存循环运行结果等。

循环程序有两种结构形式,一种是DO—WHILE结构,另一种是DO—UNTIL结构。前者把循环控制部分放在循环体的前面,先判断执行循环体的条件,满足条件就执行循环体,否则就退出循环,如图3-4(1)所示。而后者则是在执行循环体之后,再判断循环控制条件是否满足,若满足条件,则继续执行循环操作,否则,则退出循环。如图4-4(2)所示。DO—WHILE结构的循环程序,其循环体有可能并不执行,而DO—UNTIL循环程序的循环体至少必须执行一次。

【例6】 设内存BUFF开始的单元中依次存放着30个8位无符号数,求它们的和并放在SUM单元中,试编写程序。

分析:这是一个求累加的程序。程序如下:

MOV SI,BUFF ;设地址指针 MOV CX,30 ;设计数初值 XOR

AGAIN: ADD

ADC INC DEC JNZ

AX,AX ;设累加器初值 AL,[SI] AH,0 SI CX

AGAIN ;循环累加

MOV SUM,AX

【例7】 在给定个数的16位数串中,找出大于零、等于零和小于零的个数,并紧跟着原串存放。

分析:这是一个统计问题,须设定三个计数器分别统计三种情况下的结果。程序如下: DATA SEGMENT

BUFF DW X1,X2,X3,……,Xn

COUNT EQU $-BUFF ;此时,COUNT的值为BUFF所占的字节数 PLUSE DB ? ZERO DB ?

MINUS DB ? DATA ENDS CODE SEGMENT

ASSUME CS:CODE,DS:DATA ASSUME ES:DATA,SS:STACK BEGIN: MOV AX,DATA

AGAIN:

PLU:

ZER: NEXT:

CODE

【例8】 MOV DS,AX MOV CX,COUNT SHR

CX,1 ;相当于除2,正好为BUFF中的数据个数

MOV DX,0 ;设定计数器初值 MOV AX,0 ;设定计数器初值 LEA

BX,BUFF

CMP WORD PTR[BX],0 JAE PLU ;大于等于0,则转PIU INC AH ;

NEXT

JZ ZER ;=0,则转ZER INC DL ;>0,则统计 JMP

NEXT

INC DH ;=0,则统计

INC

BX

INC

BX

LOOP AGAIN MOV PLUS,DL MOV ZERO,DH MOV MINUS,AH MOV AX,4C00H INT 21H

ENDS END

BEGIN 在ADDR单元中存放着16位数Y的地址,试编写一程序,把Y中1的个数存入

COUNT单元中。

分析:这是一个循环统计的工作。采用DO—WHILE结构,做16次循环,每次将最高位移入CF中进行测试,先判断结果是否为0,若为0,则结束;否则统计计数后循环重复。 程序如下: DATA SEGMENT ADDR DW

NUMBER

Y ?

NUMBER DW COUNT DATA

DW ENDS

PROGRAM SEGMENT MAIN

PROC FAR

ASSUME CS:PROGRAM,DS:DATA START: PUSH DS

MOV AX,0 PUSH AX MOV AX,DATA MOV DS,AX

MOV CX,0 ;计数器初值=0 MOV BX,ADDR

MOV AX,[BX] ;取Y送AX

REPEAT: TEST AX,0FFFFH ;检测是否为全0

JZ JNS INC

EXIT ;是,则转EXIT

SHIFT ;最高位是0,则转SHIFT CX ;最高位是1,则统计计数

AX,1 ;处理下一位

SHIFT:

SHL

JMP REPEAT

EXIT:

MOV COUNT,CX RET ENDP

MAIN

PROGRAM ENDS

END

START

在实际应用中,有些问题较复杂,一重循环不够,必须使用多重循环实现,这些循环是一层套一层的,通常称为循环嵌套。

【例9】在DS所决定的数据段,从偏移地址BUFFER开始顺序存放100个无符号16位数,现要编写程序将这100个字数据从大到小排序。 分析:排序的方法有很多,在这里,我们采用冒泡法。 程序如下:

LEA DI,BUFFER ;DI作为指针,指向要排序的数据 MOV BL,99 ;循环控制初值

NEXT0:MOV SI,DI

MOV CL,BL

NEXT3:MOV AX,[SI] ;取一个数

ADD SI,2 CMP JNC

AX,[SI] ;与下一个数进行比较

NEXT5 ;大于等于时转移

MOV DX,[SI] ;否则,两数交换 MOV [SI-2],DX MOV [SI],AX

NEXT5:DEC CL ;控制进行交换的次数

习题与思考:

1.设变量单元A、B、C存放有三个数,若三个数都不为零,则求三个数的和,存放在D中;若有一个为零,则将其余两个也清零,试编写程序。

2.有一个100个字节的数据表,表内元素已按从大到小的顺序排列好,现给定一元素,试编程序在表内查找,若表内已有此元素,则结束;否则,按顺序将此元素插入表中适当的位臵,并修改表长。

3.内存中以FIRST和SECOND开始的单元中分别存放着两个16位组合的十进制(BCD码)

JNZ DEC JNZ HLT

NEXT3

BL ;修改交换的次数 NEXT0

数,低位在前。编程序求这两个数的组合的十进制和,并存到以THIRD开始的单元。 4.编写一段程序,接收从键盘输入的10个数,输入回车符表示结束,然后将这些数加密后存于BUFF缓冲区中。加密表为:

输入数字:0,1,2,3,4,5,6,7,8,9;密码数字:7,5,9,1,3,6,8,0,2,4

四、子程序设计

子程序是程序设计中经常使用的程序结构,通过把一些固定的、经常使用的功能做成子程序的形式,可以使源程序及目标程序大大缩短,提高程序设计的效率和可靠性。

对于一个子程序,应该注意它的入口参数和出口参数。入口参数是由主程序传给子程序的参数,而出口参数是子程序运算完传给主程序的结果。另外,子程序所使用的寄存器和存储单元往往需要保护,以免影响返回后主程序的运行。

主程序在调用子程序时,一方面初始数据要传给子程序,另一方面子程序运行结果要传给主程序,因此,主子程序之间的参数传递是非常重要的。

参数传递一般有三种方法实现:

(1) 利用寄存器 这是一种最常见方法,把所需传递的参数直接放在主程序的寄存器中传递给子程序。

(2) 利用存储单元 这种参数传递方法,把所需传递的参数直接放在子程序调用指令代码之后。

(3) 利用堆栈 这种方法将参数压入堆栈,在子程序运行时从堆栈中取参数。

下面我们通过实例说明子程序设计及参数传递方法。 【例10】 两个6字节数相加。

分析:将一个字节相加的程序段设计为子程序。主程序分3次调用该子程序,但每次调用的参数不同。 程序如下: DATA ADD1 ADD2 SUM

SEGMENT DB DB

FEH,86H,7CH,35H,68H,77H 45H,BCH,7DH,6AH,87H,90H

DB 6DUP(0) DB ENDS SEGMENT

COUNT DATA STACK

6

DB ENDS

100DUP(?)

STACK CODE

SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK MADD: MOV AX,DATA

MOV DS,AX MOV AX,STACK MOV SS,AX MOV SI,OFFSET MOV DI,OFFSET

ADD1 ADD2

MOV BX,OFFSET SUM

MOV CX,COUNT ;循环初值为6 CLC

AGAIN:CALL SUBADD ;调用子程序

LOOP AGAIN ;循环调用6次 MOV AX,4C00H INT

21H

出口参数:SI,DI,BX

;子程序入口参数:SI,DI,BX

SUBADD PROC ;完成一个字节相加

PUSH AX ;保护AX的值

MOV AL,[SI] ;SI是一个源操作数指针 ADC

AL,[DI] ;DI是另一个源操作数指针

MOV [BX],AL ;BX是结果操作数指针 INC INC INC POP RET

SI DI BX

AX ;恢复AX的值

SUBADD ENDP CODE

ENDS END MADD

【例11】 把内存中的字变量NUMBER的值,转换为4个ASCII码表示的十六进制数码串,串的起始地址为STRING。

分析:把内存中的字变量NUMBER的值,转换为4个ASCII码表示的十六进制数码串的工作设计成一个子程序,在这个子程序中再调用另一个子程序,由它完成从BCD码到ASCII码的转换。

程序如下:

DATA

NUMBER

STRING

DATA

CODE

ASSUME

BEGIN:

BINHEX

SEGMENT DW 25AFH DB 4DUP(?),0DH,0AH,‘$’ ENDS SEGMENT CS:CODE,DS:DATA MOV AX,DATA MOV DS,AX MOV ES,AX LEA BX,STRING PUSH BX ;将参数(结果地址指针)压入堆栈 PUSH NUMBER ;将源数据压入堆栈 CALL BINHEX ;调用子程序 LEA DI,STRING MOV AH,9 INT 21H PROC PUSH BP MOV BP,SP PUSH AX PUSH DI PUSH CX PUSH DX PUSHF ;以上为保护现场 MOV AX,[BP+4] ;取出NUMBER - 21 -

ADD DI,LENGTH STRING-1 ;使DI指向转换数据 MOV DX,AX ;保护原始数据 MOV CX,4

AX,0FH ;取低4位 AGAIN: AND

BINHEX

HEXD

ADDZ:

HEXD

CODE

CALL HEXD ;调子程序 STD STOSB ;保护转换数据 PUSH CX ;保护CX的值 MOV CL,4 SHR DX,CL MOV AX,DX POP CX LOOP AGAIN POPF POP DX POP CX POP DI POP AX POP BP RET 4 ENDP PROC ;将AL中的BCD码转换成ASCII码 CMP AL,0AH JL ADDZ ADD AL,’a’-‘0’-0AH ;小写字母转换成ASCII码,若为大写ADD AL,‘0’ ;字母,则再加ADD AL,7 RET ENDP ENDS EDN BEGIN - 22 -

(以下例子作为选讲)

【例12】 数的阶乘 1

按照阶乘的定义 n!= n-1)!

这是一个递归定义式,可采用子程序的的递归调用形式。程序如下:

DATA

NUM

FNUM

DATA

STACK

STACK

CODE

ASSUME

BEGIN:

FACTOR

SEGMENT DB 5 DW ? ENDS SEGMENT DB 100DUP(?) ENDS SEGMENT CS:CODE,DS:DATA,SS:STACK PUSH DS MOV AX,0 PUSH AX MOV CX,1 PUSH CX MOV AH,0 MOV AL,NUM CALL FACTOR MOV FNUM,AX POP CX MOV AX,4C00H INT 21H PROC CMP AX,0 JNZ IIA MOV DL,1 RET - 23 -

IIA:

PUSH AX DEC AL CALL FACT

POP

MUL CX IIA1: CL ;CALL MULT

IIA2:

MOV DX,AX RET

ENDP FACTOR

CODE

ENDS END BEGIN

习题与思考:

1.试编程序,统计由40000H开始的16K个单元中所存放的字符‚A‚的个数,并将结果存放在DX中。

2.在当前数据段(DS),偏移地址为DATAB开始的顺序80个单元中,存放着某班80个同学某门考试成绩。按要求编写程序:

①编写程序统计≥90分;80分~89分;70分~79分;60分~69分,<60分的人数各为多少,并将结果放在同一数据段、偏移地址为BTRX开始的顺序单元中。

②试编程序,求该班这门课的平均成绩为多少,并放在该数据段的AVER单元中。

3.编写一个子程序,对AL中的数据进行偶校验,并将经过校验的结果放回AL中。

4.利用上题的予程序,对80000H开始的256个单元的数据加上偶校验,试编程序。

- 24 -

第四章 汇编语言程序设计

回顾:8086的内部结构、寄存器功能和工作过程,指令格式、寻址方式和功能。

本讲重点:了解汇编的概念及其方法, 掌握汇编程序的基本格式,常用运算符的使用方法,汇编的步骤。

4.1汇编语言的基本元素

一、汇编语言的语句格式

由汇编语言编写的源程序是由许多语句(也可称为汇编指令)组成的。每个语句由1~4个部分组成,其格式是:

[标号] 指令助记符 [操作数] [;注解]

其中用方括号括起来的部分,可以有也可以没有。每部分之间用空格(至少一个)分开,一行最多可有132个字符。

1. 标识符 给指令或某一存储单元地址所起的名字。可由下列字符组成:

字母:A ~ z ; 数字:0 ~ 9 ; 特殊字符:?、· 、@、一、$ 。

数字不能作标识符的第一个字符,而圆点仅能用作第一个字符。标识符最长为31个字符。当标识符后跟冒号时,表示是标号。它代表该行指令的起始地址;当标识符后不带冒号时,表示变量;伪指令前的标识符不加冒号。

2. 指令助记符 表示不同操作的指令,可以是8086的指令助记符,也可以是伪指令。

3. 操作数

例如: RET ;无操作数

COUNT: INC CX ;一个操作数

如果是伪指令,则可能有多个操作数,例如:

COST DB 3,4,5,6,7 ;5个操作数

MOV AX,[BP+4] ;第二个操作数为表达式

4. 注解

该项可有可无,是为源程序所加的注解,用于提高程序的可读性。

二、汇编语言的运算符

1. 算术运算符、逻辑运算符和关系运算符

算术运算符可以应用于数字操作数,结果也是数字。而应用于存储器操作数时,只有+、- 运算符有意义。

2. 取值运算符SEG、OFFSET、TYPE、SIZE和LENGTH

例如,定义: SLOT DW 25

则: MOV AX,SLOT;从SLOT地址中取一个字送入AX

MOV AX,SEG SLOT;将SLOT所在段的段地址送入AX

MOV AX,OFFSET SLOT;将SLOT所在段的段内偏移地址送AX

· 值如表4-1所示。

DB/DW/DD等定义的操作数) 例如:若 MULT-WORD DW 50DUP(0)

则 LENGTH(MULT-WORD)=50

SIZE(MULT-WORD)=100

注意:SIZE(X)=(LENGTH X)* (TYPE X)

3. 属性运算符

常用的有:

(1) 合成运算符PTR

它作用于操作数时,则忽略了操作数当前的类型(字节或字)及属性(NEAR或FAR),而给出一个临时的类型或属性, 一般格式:类型 PTR 表达式 功能:建立一个存储器地址操作数,它与其后的存储器地址操作数有相同的段地址偏移量,但有不同的类型。

例如:SLOT DW 25

此时SLOT已定义成字单元。若我们想取出它的第一个字节内容,则可用PTR对其作用,表4-1存储器操作数的类型属性及返回值

使它暂时改变为字节单元,即 MOV AL,BYTE PTR SLOT

三、表达式

由运算符和操作数组成的序列,在汇编时产生一个确定的值。这个值可以仅表示一个常量,也可以表示一个存储单元的偏移地址,相应的表达式称为常量表达式和地址表达式。

1. 常数

汇编语言语句中出现的常数可以有7种:

① 二进制数 后跟字母B,如01000001B。

② 八进制数 后跟字母Q或O,如202Q或202O。

③ 十进制数 后跟D或不跟字母,如85D或85。

④ 十六进制数 后跟H,如56H,0FFH。注意,当数字的第一个字符是A~F时,在字符前应添加一个数字0,以示和变量的区别。

另有,十进制浮点数、十六进制实数、字符和字符串

2. 常量操作数

可以为数字常量操作数或字符串常量操作数。前者可采用二进制、八进制、十进制或十六进制等计数形式;而后者则为相应字符的ASCII码。

3. 存储器操作数

存储器操作数是一个地址操作数,代表一个存储单元的地址,通常以标识符的形式出现。 存储器操作数可以分为变量及标号两种类型,如果存储器操作数所代表的是某个数据在数据段、附加段或堆栈段中的地址,那么这个存储器操作数就称为变量;如果存储器操作数所代表的是某条指令代码在代码段中的地址,那么这个存储器操作数称为标号。变量所对应的存储单元内容在程序的运行过程中是可以改变的,标号通常作为转移指令或调用指令的目标操作数,在程序运行过程中不能改变。

(1) 段地址:即存储器操作数所对应的存储单元所在段的段地址;

(2) 偏移地址:即存储器操作数所对应的存储单元在所在段内的偏移地址;

(3) 类型:变量的类型是相应存储单元所存放的数据项的字节数;而标号的类型则反映了相应存储单元地址在作为转移或调用指令的目标操作数时的寻址方式,可有两种情况,即 NEAR和FAR。具体值可见表4-1

4. 常量表达式

由常量操作数及运算符构成,在汇编时产生一个常量。

如PORT、VAL+1、 OFFSET SUM、SEG SUM、TYPE CYCLE等。

5. 地址表达式 由存储器操作数与运算符构成,必须有明确的物理意义。

例如 SUM+2、CYCLE-5

表达式SUM+2、CYCLE-5的值仍然是一个存储器操作数,该存储器操作数的段地址与类型属性分别与存储器操作数SUM及CYCLE相同,但偏移地址分别比SUM及CYCLE大2或小5。表达式是在汇编时计算的,而变量单元的内容在程序的运行过程中可以改变。

四、汇编语言程序汇编步骤

汇编语言程序要能在机器上运行,还必需将汇编源程序汇编成可执行程序。为此必须完成以下几个步骤。

1. 编辑源程序

2. 调用宏汇编对源程序进行汇编

3. 对目标程序进行连接

4. 运行可执行程序并调试

【习题与思考】

1.下列语句在存储器中分别为变量分配多少字节空间?并画出存储空间的分配图。 VAR1 DB

VAR2

VAR3

VAR4 10,2 5DUP(?),0 ‘HOW ARE YOU?’,‘$’ -1,1,0 DW DB DD

2. 假定VAR1和VAR2为字变量,LAB为标号,试指出下列指令的错误之处。

(1)ADD

(3)JMP VAR1,VAR2 (2)SUB AL,VAR1 LAB[SI] (4)JNZ VAR1

3. 对于下面的符号定义,指出下列指令的错误。

A1 DB ? A2 DB 10 K1 EQU 1024

(1) MOV

(3)CMP

K1,AX A1,A2 (2)MOV A1,AX EQU 2048 (4)K1

4.2 伪指令

【回顾】汇编的概念及其方法, 掌握汇编程序的基本格式。8086/8088的指令格式。

【本讲重点】了解伪指令的功能,掌握定义数据、符号、段、过程等伪指令的使用方法,能编写格式正确的汇编程序。

4.2 伪指令

伪指令用来对汇编程序进行控制,对程序中的数据实现条件转移、列表、存储空间分配等处理,其格式和汇编指令一样,但一般不产生目的代码,即不直接命令CPU去执行什么操作。

一、定义数据伪指令

· DB:定义字节,即每个数据是1个字节。

· DW:定义字,即每个数据占1个字(2个字节)。

· DD:定义双字,即每个数据占2个字。低字部分在低地址,高字部分在高地址。 · DQ:定义4字长,即每个数据占4个字。

· DT:定义10个字节长,用于压缩式十进制数,

例如:DATA1 DB 5,6,8,100

DATA2 DW 7,287

TABLE DB ? ;表示在TABLE单元中存放的内容是随机的;

当一个定义的存储区内的每个单元要放臵同样的数据时,可用DUP操作符。 一般格式:COUNT DUP(?),COUNT 为重复的次数,‚( )‛中为要重复的数据。 如:BUFFER DB 100 DUP(0) ;表示以BUFFER为首地址的100个字节中存放00H数据

BUFFER1 DB 100 DUP(3,5,2DUP(10),35),24,‘NUM’)

想一想存储区的情况?

二、符号定义伪指令EQU、=、及PURGE

· EQU 伪指令给符号定义一个值。在程序中,凡是出现该符号的地方,汇编时均用其值代替,

如:TIMES EQU 50

DATA DB TIMES DUP(?)

上述两个语句实际等效于如下一条语句:

DATA DB 50 DUP(?)

· ‚=‛伪指令可给初始变量赋值。

如:COUNT=100 ;COUNT=100

TIME=50 ;TIME=50

· PURGE伪指令用于释放由EQU伪指令定义的变量,使这些变量可以被重新定义。 PURGE TIMES ; 释放TIMES变量

TIMES EQU 2 ; 重新定义

三、段定义伪指令SEGMENT和ENDS

一般来说,一个完整的汇编源程序由3个段组成,即堆栈段、数据段和代码段。段定义伪指令可将源程序划分成若干段,以便生成目的代码和连接时将各同名段进行组合。 :

段名 SEGMENT [定位类型] [组合类型] [类别]

段名 ENDS

SEGMENT和ENDS应成对使用,缺—不可。其中段名是不可省略的。其它是可选项,是赋予段名的属性,可以省略。

例如: DATA SEGMENT

DW 20DUP(?)

DATA ENDS

四、设定段寄存器伪指令ASSUME 一般格式: ASSUME 段寄存器:段名[,段寄存器:段名,……] 功能:通知汇编程序,哪一个段寄存器是该段的段寄存器,以便对使用变量或标号的指令汇

编出正确的目的代码。在段名中,CODE表示代码段,DATA表示数据段,STACK表示堆栈段。

由于ASSUME伪指令只指明某一个段地址应存于哪一个段寄存器中,并没有包含将段地址送入该寄存器的操作。因此要将真实段地址装入段寄存器还需用汇编指令来实现。这一步是不可缺少的。

例如,CODE SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK

MOV AX,DATA ;DATA段值送AX

MOV DS,AX ;AX内容送DS,DS才有实际段值

CODE ENDS

当程序运行时,由于DOS的装入程序负责把CS初始化成正确的代码段地址,SS初始化为正确的堆栈段地址,因此用户在程序中就不必设臵。但是,在装入程序中DS寄存器由于被用作其它用途,因此,在用户程序中必须用两条指令对DS进行初始化,以装入用户的数据段地址。当使用附加段时,也要用MOV指令给ES赋段地址。

五、定义过程的伪指令PROC和ENDP

在程序设计中,可将具有一定功能的程序段看成为一个过程(相当于一个子程序),它可以被别的程序调用。

一个过程由伪指令PROC和ENDP来定义,其格式为:

过程名 PROC [类型]

过程体

RET

过程名 ENDP

其中过程名是为过程所起的名称,不能省略,过程的类型由FAR(远过程,为段间调用)和NEAR(近过程,在本段内调用)来确定,如果缺省类型,则该过程就默认为近过程。ENDP表示过程结束。过程体内至少应有一条RET指令,以便返回被调用处。过程可以嵌套,也可以递归使用。

例如一个延时100ms的子程序,其过程可定义如下,

DELAY PROC

PUSH BX

PUSH CX

MOV BL,10

;延时10ms,改变BL和CX中的值,即可改变延时时间。

AGAIN: MOV CX,2801 ;

WAIT; LOOP WAIT

DEC BL

JNZ AGAIN

POP CX

POP BX

RET

DELAY ENDP

CALL DELAY ;调用该过程

远过程调用时被调用过程必定不在本段内。

例如,有两个程序段,其结构如下:

CODE1 SEGMENT

ASSUME CS:CODE1

FARPROC PROC FAR

RET

FARPROC ENDP

CODE1 ENDS

CODE2 SEGMENT

ASSUME CS:CODE2

CALL FARPROC

…..

CODE2 ENDS

CODE1 段中的FARPROC 过程被另一段CODE2调用,故为远过程。

六、宏指令

在汇编语言书写的源程序中,若有的程序段要多次使用,为了简化程序书写,该程序段可以用一条宏指令来代替,而汇编程序汇编到该宏指令时,仍会产生源程序所需的代码。 宏指令的一般格式为:宏指令名 MACRO [形式参量表]

宏体

ENDM

例如:SHIFT MACRO

MOV CL,4

SAL AL,Cl

ENDM

这样定义以后,凡是要使AL中内容左移4位的操作都可用一条宏指令SHIFT来代替。

宏指令与子程序有许多类似之处。它们都是一段相对独立的、完成某种功能的、可供调用的程序模块,定义后可多次调用。但在形成目的代码时,子程序只形成一段目的代码,调

用时转来执行。而宏指令是将形成的目的代码插到主程序调用的地方。因此,前者占内存少,但执行速度稍慢;后者刚好相反。

七、ORG 伪指令

ORG伪指令规定了在某一段内,程序或数据代码存放的起始偏移地址。

: ORG

例如:DATA

BUFF1 SEGMENT DB 23,56H,‘EOF’ ORG 2000H BUFF2 DATA DB ‘STRING’ ENDS

上述变量定义中,BUFF1从DATA段偏移地址为0的单元开始存放,而BUFF2则从DATA段偏移为2000H的单元开始存放,两者不是连续存放。

八、汇编结束伪指令END

该伪指令表示源程序的结束.令汇编程序停止汇编。因此,任何一个完整的源程序均应有END指令。 一般格式: END [表达式]

其中表达式表示该汇编程序的启动地址。例如:

END START ;表明该程序的启动地址为START。

习题与思考:

1.数据定义语句如下所示:

FIRST DB 90H,5FH,6EH,69H

SECOND DB 5 DUP(?)

THIRD DB 5 DUP(?)

FORTH DB 5 DUP(?)

自FIRST单元开始存放的是一个四字节的十六进制数(低位字节在前),要求:

(1)编一段程序将这个数左移两位、右移两位后存放到自SECOND开始的单元(注意保留

移出部分。

(2)编一段程序将这个数求补以后存放到自FORTH开始的单元。

2.试编程序将内存从40000H到4BFFFH的每个单元中均写入55H,并再逐个单元读出比较,看写入的与读出的是否一致。若全对,则将AL臵7EH;只要有错,则将AL臵81H。

3.在当前数据段4000H开始的128个单元中存放一组数据,试编程序将它们顺序搬移到A000H开始的顺序128个单元中,并将两个数据块逐个单元进行此较;若有错将BL臵00H;全对则将BL臵FFH,试编程序。

3.3汇编程序设计

【回顾】8086的指令系统,汇编程序的基本格式,伪指令的功能,汇编程序的正确格式。

【本讲重点】掌握汇编程序的分析与设计方法(包括分支程序和循环程序)

一、简单程序设计

简单程序设计是没有分支,没有循环的直线运行程序,程序执行按照IP内容自动增加的顺序进行。

【例1】 利用查表法计算平方值。已知0 ~ 9的平方值连续存在以SQTAB开始的存储区域中,求SUR单元内容X的平方值,并放在DIS单元中。假定0≤X≤ 9且为整数。 分析:建立平方表,通过查表完成。

STACK SEGMENT

DB 100 DUP(?)

STACK ENDS

DATA SEGMENT

SUR DB ? DIS DB ? SQTAB DB 0,1,4,9,16,25,36,49,64,81 ; 0~9的平方表

DATA ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK,ES:DATA

BEGIN:PUSH DS

MOV AX,0 PUSH AX ;保证返回DOS, MOV AX,DATA

MOV DS,AX ;为DS送初值

LEA BX,SQTAB ;以下程序部分完成查表求平方值 MOV AH,0 ;亦可用查表指令完成(如下程序段) MOV AL,SUR ;AL=X LEA BX, SQTAB ADD

BX,AX ; MOV AL, SUR

MOV AL,[BX] ; XLAT MOV DIS,AL ; MOV DIS, AL

CODE ENDS

END BEGIN

【例2】已知Z=(X+Y)-(W+Z),其中X,Y,Z,W均为用压缩BCD码表示的数,写出程序。 分析:这也是一种典型的直线程序,在这里要注意是BCD数相加,要进行十进制调整。具

体程序如下:

MOV AL,Z MOV BL,W ADD DAA

MOV BL,AL ; BL=(W+Z) MOV AL,X MOV DL,Y ADD

AL,DL ; AL=(X+Y) AL,BL

DAA ;十进制调整 SUB

AL,BL ; AL=(X+Y)-(Z+W)

DAS ;十进制调整

MOV Z,AL ;结果送Z

二、分支程序设计

分支程序的基本思想是根据逻辑判断的结果来形成程序的分支,如图,若A成立,则执行P1;否则执行P2。

【例3】 试编写程序段,实现符号函数。

分析:变量X的符号函数可表示为:

1 X>0 Y= 0 X=0

-1 X

程序可通过对符号标志的判别来确定执行哪一分支。 START: MOV AX,BUFFER ;(BUFFER)=X

OR JE JNS

AX,AX

ZERO ;X=0,则转ZERO PLUS ;X为正数,则转PLUS

MOV BX,0FFFFH ;X为负数,则-1送BX JMP

CONT1

表4-2 子程序R1—R8的入口地址表 ZERO:

MOV BX,0

CONT1

JMP

PLUS: MOV BX,1

CONT1: ……

【例4】 利用表实现分支

根据AL中各位被臵位情况,控制转移到8个子程序P1~P8之一中去。转移表的结构如表4-2所示。

分析:对于这种程序关键要找出每种情况的转移地址,从图中可见

表地址=表基地址+偏移量, 而偏移量可由AL各位所在位臵*2求得。 DATA BASE

SEGMENT

DW SR0,SR1,SR2,SR3, SR4,SR5,SR6,SR7

DATA CODE

ENDS

SEGMENT

ASSUME CS:CODE,DS:DATA,ES:DATA BEGIN: PUSH DS

XOR AX,AX

PUSH AX

MOV AX,DATA MOV DS,AX LEA IN

BX,BASE ;表头送BX AL,PORT

GETBIT: RCR AL,1 ;右移一位

JC GETAD ;移出位是1? INC

BX

INC BX ;修改指针

JMP

GETBI

GETAD: JMP WORD PTR[BX] ;实现散转 CODE

ENDS

END BEGIN

根据跳转表构成方法不同,实现分支的方法也有所改变,下面有三个问题希望大家思考: (1) 若跳转表地址由段值和偏移量四个字节构成,程序应如何实现?

(2) 若跳转表中的内容由JMP OPRD指令构成,表的结构应如何组织、程序如何实现? (3) 上述程序若不用间接跳转指令,而改为直接跳转,程序如何变动? 【例5】 将内存中某一区域的原数据块传送到另一区域中。

分析 这种程序若源数据块与目的数据块之间地址没有重叠,则可直接用传送或串操作实现;若地址重叠,则要先判断源地址+数据块长度是否小于目的地址,若是,则可按增量方式进行,否则要修改指针指向数据块底部,采用减量方式传送。程序如下: DATA STR

SEGMENT

DB 1000DUP(?)

STR+7 STR+25

50

STR1 STR2

EQU EQU

STRCOUNT DATA STACK STAPN STACK

EQU

ENDS

SEGMENT PARA STACK ‘STACK’ DB ENDS

100DUP(?)

CODE

SEGMENT

ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK PROC PUSH DS SUB

AX,AX

GOO

PUSH AX MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX

MOV CX,STRCOUNT MOV SI,STR1 MOV DI,STR2 CLD PUSH SI ADD CMP POP JL STD ADD ADD REP RET ENDP ENDS END

GOO 图4-4 循环结构示意图 SI,STRCOUNT-1 DI,STRCOUNT-1

MOVSB SI,STRCOUNT-1 SI,DI OK

SI

OK:

GOO

CODE

三、循环程序设计

循环程序是经常遇到的程序结构,一个循环结构通常由以下几个部分组成。 1. 循环初始化部分 一般要进行地址指针、循环次数及某标志的设臵,相关寄存器的清零

等操作。只有正确地进行了初始化设臵, 循环程序才能正确运行,及时停止。

2. 循环体 是要求重复执行的程序段部分,对应于要求重复执行的操作。 3. 循环控制部分 由该部分修改并判断控制循环的条件是否满足。以决定是否继续循环。 4. 循环结束部分 如保存循环运行结果等。

循环程序有两种结构形式,一种是DO—WHILE结构,另一种是DO—UNTIL结构。前者把循环控制部分放在循环体的前面,先判断执行循环体的条件,满足条件就执行循环体,否则就退出循环,如图3-4(1)所示。而后者则是在执行循环体之后,再判断循环控制条件是否满足,若满足条件,则继续执行循环操作,否则,则退出循环。如图4-4(2)所示。DO—WHILE结构的循环程序,其循环体有可能并不执行,而DO—UNTIL循环程序的循环体至少必须执行一次。

【例6】 设内存BUFF开始的单元中依次存放着30个8位无符号数,求它们的和并放在SUM单元中,试编写程序。

分析:这是一个求累加的程序。程序如下:

MOV SI,BUFF ;设地址指针 MOV CX,30 ;设计数初值 XOR

AGAIN: ADD

ADC INC DEC JNZ

AX,AX ;设累加器初值 AL,[SI] AH,0 SI CX

AGAIN ;循环累加

MOV SUM,AX

【例7】 在给定个数的16位数串中,找出大于零、等于零和小于零的个数,并紧跟着原串存放。

分析:这是一个统计问题,须设定三个计数器分别统计三种情况下的结果。程序如下: DATA SEGMENT

BUFF DW X1,X2,X3,……,Xn

COUNT EQU $-BUFF ;此时,COUNT的值为BUFF所占的字节数 PLUSE DB ? ZERO DB ?

MINUS DB ? DATA ENDS CODE SEGMENT

ASSUME CS:CODE,DS:DATA ASSUME ES:DATA,SS:STACK BEGIN: MOV AX,DATA

AGAIN:

PLU:

ZER: NEXT:

CODE

【例8】 MOV DS,AX MOV CX,COUNT SHR

CX,1 ;相当于除2,正好为BUFF中的数据个数

MOV DX,0 ;设定计数器初值 MOV AX,0 ;设定计数器初值 LEA

BX,BUFF

CMP WORD PTR[BX],0 JAE PLU ;大于等于0,则转PIU INC AH ;

NEXT

JZ ZER ;=0,则转ZER INC DL ;>0,则统计 JMP

NEXT

INC DH ;=0,则统计

INC

BX

INC

BX

LOOP AGAIN MOV PLUS,DL MOV ZERO,DH MOV MINUS,AH MOV AX,4C00H INT 21H

ENDS END

BEGIN 在ADDR单元中存放着16位数Y的地址,试编写一程序,把Y中1的个数存入

COUNT单元中。

分析:这是一个循环统计的工作。采用DO—WHILE结构,做16次循环,每次将最高位移入CF中进行测试,先判断结果是否为0,若为0,则结束;否则统计计数后循环重复。 程序如下: DATA SEGMENT ADDR DW

NUMBER

Y ?

NUMBER DW COUNT DATA

DW ENDS

PROGRAM SEGMENT MAIN

PROC FAR

ASSUME CS:PROGRAM,DS:DATA START: PUSH DS

MOV AX,0 PUSH AX MOV AX,DATA MOV DS,AX

MOV CX,0 ;计数器初值=0 MOV BX,ADDR

MOV AX,[BX] ;取Y送AX

REPEAT: TEST AX,0FFFFH ;检测是否为全0

JZ JNS INC

EXIT ;是,则转EXIT

SHIFT ;最高位是0,则转SHIFT CX ;最高位是1,则统计计数

AX,1 ;处理下一位

SHIFT:

SHL

JMP REPEAT

EXIT:

MOV COUNT,CX RET ENDP

MAIN

PROGRAM ENDS

END

START

在实际应用中,有些问题较复杂,一重循环不够,必须使用多重循环实现,这些循环是一层套一层的,通常称为循环嵌套。

【例9】在DS所决定的数据段,从偏移地址BUFFER开始顺序存放100个无符号16位数,现要编写程序将这100个字数据从大到小排序。 分析:排序的方法有很多,在这里,我们采用冒泡法。 程序如下:

LEA DI,BUFFER ;DI作为指针,指向要排序的数据 MOV BL,99 ;循环控制初值

NEXT0:MOV SI,DI

MOV CL,BL

NEXT3:MOV AX,[SI] ;取一个数

ADD SI,2 CMP JNC

AX,[SI] ;与下一个数进行比较

NEXT5 ;大于等于时转移

MOV DX,[SI] ;否则,两数交换 MOV [SI-2],DX MOV [SI],AX

NEXT5:DEC CL ;控制进行交换的次数

习题与思考:

1.设变量单元A、B、C存放有三个数,若三个数都不为零,则求三个数的和,存放在D中;若有一个为零,则将其余两个也清零,试编写程序。

2.有一个100个字节的数据表,表内元素已按从大到小的顺序排列好,现给定一元素,试编程序在表内查找,若表内已有此元素,则结束;否则,按顺序将此元素插入表中适当的位臵,并修改表长。

3.内存中以FIRST和SECOND开始的单元中分别存放着两个16位组合的十进制(BCD码)

JNZ DEC JNZ HLT

NEXT3

BL ;修改交换的次数 NEXT0

数,低位在前。编程序求这两个数的组合的十进制和,并存到以THIRD开始的单元。 4.编写一段程序,接收从键盘输入的10个数,输入回车符表示结束,然后将这些数加密后存于BUFF缓冲区中。加密表为:

输入数字:0,1,2,3,4,5,6,7,8,9;密码数字:7,5,9,1,3,6,8,0,2,4

四、子程序设计

子程序是程序设计中经常使用的程序结构,通过把一些固定的、经常使用的功能做成子程序的形式,可以使源程序及目标程序大大缩短,提高程序设计的效率和可靠性。

对于一个子程序,应该注意它的入口参数和出口参数。入口参数是由主程序传给子程序的参数,而出口参数是子程序运算完传给主程序的结果。另外,子程序所使用的寄存器和存储单元往往需要保护,以免影响返回后主程序的运行。

主程序在调用子程序时,一方面初始数据要传给子程序,另一方面子程序运行结果要传给主程序,因此,主子程序之间的参数传递是非常重要的。

参数传递一般有三种方法实现:

(1) 利用寄存器 这是一种最常见方法,把所需传递的参数直接放在主程序的寄存器中传递给子程序。

(2) 利用存储单元 这种参数传递方法,把所需传递的参数直接放在子程序调用指令代码之后。

(3) 利用堆栈 这种方法将参数压入堆栈,在子程序运行时从堆栈中取参数。

下面我们通过实例说明子程序设计及参数传递方法。 【例10】 两个6字节数相加。

分析:将一个字节相加的程序段设计为子程序。主程序分3次调用该子程序,但每次调用的参数不同。 程序如下: DATA ADD1 ADD2 SUM

SEGMENT DB DB

FEH,86H,7CH,35H,68H,77H 45H,BCH,7DH,6AH,87H,90H

DB 6DUP(0) DB ENDS SEGMENT

COUNT DATA STACK

6

DB ENDS

100DUP(?)

STACK CODE

SEGMENT

ASSUME CS:CODE,DS:DATA,SS:STACK MADD: MOV AX,DATA

MOV DS,AX MOV AX,STACK MOV SS,AX MOV SI,OFFSET MOV DI,OFFSET

ADD1 ADD2

MOV BX,OFFSET SUM

MOV CX,COUNT ;循环初值为6 CLC

AGAIN:CALL SUBADD ;调用子程序

LOOP AGAIN ;循环调用6次 MOV AX,4C00H INT

21H

出口参数:SI,DI,BX

;子程序入口参数:SI,DI,BX

SUBADD PROC ;完成一个字节相加

PUSH AX ;保护AX的值

MOV AL,[SI] ;SI是一个源操作数指针 ADC

AL,[DI] ;DI是另一个源操作数指针

MOV [BX],AL ;BX是结果操作数指针 INC INC INC POP RET

SI DI BX

AX ;恢复AX的值

SUBADD ENDP CODE

ENDS END MADD

【例11】 把内存中的字变量NUMBER的值,转换为4个ASCII码表示的十六进制数码串,串的起始地址为STRING。

分析:把内存中的字变量NUMBER的值,转换为4个ASCII码表示的十六进制数码串的工作设计成一个子程序,在这个子程序中再调用另一个子程序,由它完成从BCD码到ASCII码的转换。

程序如下:

DATA

NUMBER

STRING

DATA

CODE

ASSUME

BEGIN:

BINHEX

SEGMENT DW 25AFH DB 4DUP(?),0DH,0AH,‘$’ ENDS SEGMENT CS:CODE,DS:DATA MOV AX,DATA MOV DS,AX MOV ES,AX LEA BX,STRING PUSH BX ;将参数(结果地址指针)压入堆栈 PUSH NUMBER ;将源数据压入堆栈 CALL BINHEX ;调用子程序 LEA DI,STRING MOV AH,9 INT 21H PROC PUSH BP MOV BP,SP PUSH AX PUSH DI PUSH CX PUSH DX PUSHF ;以上为保护现场 MOV AX,[BP+4] ;取出NUMBER - 21 -

ADD DI,LENGTH STRING-1 ;使DI指向转换数据 MOV DX,AX ;保护原始数据 MOV CX,4

AX,0FH ;取低4位 AGAIN: AND

BINHEX

HEXD

ADDZ:

HEXD

CODE

CALL HEXD ;调子程序 STD STOSB ;保护转换数据 PUSH CX ;保护CX的值 MOV CL,4 SHR DX,CL MOV AX,DX POP CX LOOP AGAIN POPF POP DX POP CX POP DI POP AX POP BP RET 4 ENDP PROC ;将AL中的BCD码转换成ASCII码 CMP AL,0AH JL ADDZ ADD AL,’a’-‘0’-0AH ;小写字母转换成ASCII码,若为大写ADD AL,‘0’ ;字母,则再加ADD AL,7 RET ENDP ENDS EDN BEGIN - 22 -

(以下例子作为选讲)

【例12】 数的阶乘 1

按照阶乘的定义 n!= n-1)!

这是一个递归定义式,可采用子程序的的递归调用形式。程序如下:

DATA

NUM

FNUM

DATA

STACK

STACK

CODE

ASSUME

BEGIN:

FACTOR

SEGMENT DB 5 DW ? ENDS SEGMENT DB 100DUP(?) ENDS SEGMENT CS:CODE,DS:DATA,SS:STACK PUSH DS MOV AX,0 PUSH AX MOV CX,1 PUSH CX MOV AH,0 MOV AL,NUM CALL FACTOR MOV FNUM,AX POP CX MOV AX,4C00H INT 21H PROC CMP AX,0 JNZ IIA MOV DL,1 RET - 23 -

IIA:

PUSH AX DEC AL CALL FACT

POP

MUL CX IIA1: CL ;CALL MULT

IIA2:

MOV DX,AX RET

ENDP FACTOR

CODE

ENDS END BEGIN

习题与思考:

1.试编程序,统计由40000H开始的16K个单元中所存放的字符‚A‚的个数,并将结果存放在DX中。

2.在当前数据段(DS),偏移地址为DATAB开始的顺序80个单元中,存放着某班80个同学某门考试成绩。按要求编写程序:

①编写程序统计≥90分;80分~89分;70分~79分;60分~69分,<60分的人数各为多少,并将结果放在同一数据段、偏移地址为BTRX开始的顺序单元中。

②试编程序,求该班这门课的平均成绩为多少,并放在该数据段的AVER单元中。

3.编写一个子程序,对AL中的数据进行偶校验,并将经过校验的结果放回AL中。

4.利用上题的予程序,对80000H开始的256个单元的数据加上偶校验,试编程序。

- 24 -


相关内容

  • 谈电子信息工程技术论文
  • 论 文 谈电子信息工程技术 学院:信息学院 专业:电子信息工程技术 学号:1302xxxxx 姓名:李健 2015年5月15日 谈电子信息工程技术 摘 要:电子信息工程专业概况,电子信息技术包含的技术方向,电子专业主干课程简介,电子信息工程技术在生活中的应用.从电子信息工程谈未来就业趋向和专业所需要 ...

  • 电子信息参考书 中文2
  • 垂直领域元件构件器件基本成分 以下全部是书名,用空格隔开 集成电路 RF MEMS 理论·设计·技术 二极管 晶体管原理与设计(第2版)("十一五"规划教材) 双色图文详解二极管及应用电路 功率晶体管原理 三极管 场效应管 电容器 图书:电容器及其应用 电容器手册 电化学电容器电 ...

  • 实用软件工程方法
  • 实用软件工程 教学大纲 1.1 课程简介 1.1.1 课程名称 中文名:实用软件工程方法 英文名:Introduction to Software Engieering 1.1.2 课程类别 岗位应用技能课程 1.1.3 课程概览 本书为软件工程的初学者介绍一个实用的软件开发框架,它只说明怎样去做软 ...

  • 51单片机C语言编程实例
  • 学好单片机必备知识 单片机的外部结构: 1. DIP40双列直插: 2. P0,P1,P2,P3四个8位准双向I/O引脚:(作为I/O输入时,要先输出高电平) 3. 电源VCC(PIN40)和地线GND(PIN20): 4. 高电平复位RESET(PIN9):(10uF电容接VCC与RESET,即可 ...

  • 单片机课程标准
  • 山东电子职业技术学院 <单片机技术及应用> 课程标准 课程代码:03021038 适用专业:电子信息工程技术专业 编 者:电子信息工程技术专业教研室(单片机课程组) 课程负责人:栾秋平 审 核 人:栾秋平 编制单位:山东电子职业技术学院电子工程系(部) 合作单位:积成电子股份有限公司 编 ...

  • 九年级第一学期信息技术教案_10
  • 九年级第一学期信息技术教案_10 [课 题] 机器人编程(仿真)软件简介 [授课时间] [教学目标] [知识目标] 通过实例了解和掌握机器人编程(仿真)软件的使用. [技能目标] 机器人编程(仿真)软件的使用方法 [情感目标] 通过编程(仿真)软件的使用,初步体验程序编制的乐趣. [教学重点] 1. ...

  • 计算机信息类综合实验课程设计与实践
  • !墅堕!!!!二!!!! CNll一2034/T 实验技术与管理 ExperimentalTechnologyand 第32卷第4期2015年4月 V01.32 No.4 Apr.2015 Management 实验课程改革 计算机信息类综合实验课程设计与实践 王变琴,刘树郁,许海州,刘 (中山大学 ...

  • MATLAB在波动光学教学中的应用
  • 第21卷第2期(2005) 河西学院学报 Vol.21 No.2(2005) MATLAB在波动光学教学中的应用 韩振海 贺德春 (河西学院物理系,甘肃 张掖 734000) 摘 要:在介绍MATLAB语言的特点并分析目前光学课程教学现代化过程中所存在问题的基础上,通过教学仿真实例阐述了数学软件Ma ...

  • 在单片机教学中应用"边学边做"的教学模式实例
  • 随着电子行业完成了产业升级,由单一的硬件开发技术发展到以单片机技术为标志性的硬件+软件的二级开发技术,电子产品已经走向了微型化.智能化的道路.在中等职业学校推行单片机教学是中等职业教育发展的需要和必然的选择. 单片机技术是一门实践性很强的技术,是融知识性与技能性于一体的课程.提高学生的单片机理论水平 ...