4.4 子程序与扩展子程序
在程序设计中,常常会遇到 某些功能完全相同的程序段 在同一程序的多处或不同程序中出现,
如求一个数的阶乘,比较两数大小,求字符串的长度,数制转换等等。为了节省存贮空间,
减少编制程序的重复劳动,可以将这些多次重复的程序段从整个程序中独立出来,附加一些额外语句,将它编制成一种具有公用性的,独立的程序段 —— 子程序,并通过适当的方法把它与其他程序段链接起来,这种程序设计的方法就称为 子程序设计 。
子程序结构的优点:
可以减小程序的长度,节省了计算机汇编源程序的时间和程序的存储空间。
子程序可以重复使用,使得程序设计时间可以大量节省。
增加了程序的可读性,方便对程序的修改、调试。
子程序是模块化、结构化、自顶向下程序设计的基础
MASM6.x子程序定义的方法
一般过程定义(子程序)伪指令。
子程序在汇编语言中也称为过程
( Procedure),它相当于高级语言 C++
的函数和 VB的过程
扩展过程定义(扩展子程序)伪指令。
一般过程定义(子程序)伪指令
过程定义的一般格式为:
过程名 PROC [NEAR/FAR]
.
,;过程定义体
.
RET
过程名 ENDP
过程名由程序员来命名,命名方法同变量名,
同一源程序中不能有相同的过程名。
PROC为过程定义开始伪指令,ENDP为过程定义结束伪指令,PROC-ENDP必须配对使用;配对的 PROC-ENDP前面的过程名应相同。
NEAR/FAR定义了过程的属性,NEAR属性的过程只能被相同代码段的程序调用,称为段内近调用; FAR 属性的过程只能被相同或不同代码段的程序调用,称为段间远调用。
子程序的结构形式
一个完整的子程序一般包括下列内容:
(1) 子程序的说明部分
(2) 子程序的调用和返回
(3) 子程序的现场保护和现场恢复子程序的说明部分
子程序的说明部分一般包括如下内容,
(1)子程序名。命名时要见名知意。
(2)子程序的功能。说明子程序完成的任务。
(3)子程序的入口参数。说明子程序运行所需参数及存放位置。
(4)子程序的出口参数。说明子程序的运行结果的参数及存放位置。
(5)子程序所占用的寄存器和工作单元。
(6)子程序调用示例。
举例;子程序名,B16TOD;子程序的功能:完成将一个 16位二进制数(无符号数)转换为对应的十进制数的; ASCII码,存放在共享存储区 SHALLUNIT;子程序的入口参数:存放在 BINNUM存储单元;子程序的出口参数:存放在共享存储区
SHALLUNIT和共享存储单元 ADDRESS;子程序所占用的寄存器和,AX,DX,SI,BX;子程序调用示例:
子程序的调用和返回
.
.
.
.
.
.
.
.
.
.
.
.
retret
c a l l s u b 2c a l l s u b 1
.
.
.
.
.
.
main
sub1 sub2
end
返回地址
CALL指令的执行分成两步,
第一步,保护返回地址( CALL指令下一条指令的地址),利用堆栈实现,即将返回地址压入堆栈;
第二步,转向子程序,即把子程序的首地址送入 IP或 CS:IP
RET指令的功能是返回主程序,即把子程序的返回地址送入 IP或 CS:IP。
子程序的现场保护和现场恢复
在主程序调用子程序时已经占用了一定数量的寄存器,子程序执行时又要使用寄存器,子程序执行完返回主程序后,
又要保证主程序按原来状态继续执行,
这就需要对那些在主程序中使用过了的寄存器,在子程序中又要用,且它们在主程序中的值又不能被覆盖时(因为子程序返回后主程序还要使用),这些寄存器的内容在子程序体执行之前必须加以保护,这就称为现场保护
子程序执行完再恢复这些寄存器的内容,
称为现场恢复。子程序的现场保护和现场恢复既可以在主程序中完成,也可以在子程序中完成,但为了使程序结构清晰,我们一般在子程序中完成现场保护和现场恢复。
子程序的现场保护和现场恢复方法有两种利用堆栈实现现场保护和现场恢复(利用 PUSH/POP指令实现)。
利用内存单元实现现场保护和现场恢复
(利用 MOV指令实现)。
利用堆栈实现现场保护和恢复过程名 PROC [NEAR/FAR]
PUSH AX.
.
,;保护现场
.
PUSH DX
.
,;过程定义体
.
POP DX
.
,;恢复现场
.
POP AX
RET
过程名 ENDP
4.4.2 子程序参数传递方法
主程序在调用子程序之前,必须把要子程序处理的数据传送给子程序,这些加工处理的数据就称为子程序的输入参数,
也叫入口参数。
当子程序执行完返回主程序时,应该把本次处理的结果返回给主程序,这些加工处理的结果就称为输出参数,也称为子程序的出口参数。
子程序参数传递的方法
利用寄存器实现参数传递。
利用内存单元实现参数传递。
利用堆栈实现参数传递利用寄存器实现子程序参数传递
这种方法是通过寄存器存放参数来进行传递的,即在主程序调用子程序前,将入口参数送到约定寄存器中;子程序可以直接从这些寄存器中取出参数进行加工处理,并将结果也放在约定的寄存器中,然后返回主程序,主程序再从寄存器中取出结果。
利用存贮单元实现子程序参数传递
这种方法是在主程序调用子程序前,将入口参数存放到约定的存储单元(如变量)中,
子程序运行时到约定的位置读取参数;子程序执行结束将结果也放在约定单元。我们还可以通过用寄存器存放存储区首地址,来实现多参数情况下的传递。
利用堆栈实现子程序参数传递
利用堆栈实现参数传递的方法是在调用子程序之前,用 PUSH指令将子程序的入口参数压入堆栈,在子程序中通过出栈指令依次得到这些参数。经过子程序操作处理后再将子程序的出口参数压入堆栈,返回主程序后再通过出栈指令获得结果。一般只通过堆栈传入口参数。
利用堆栈实现参数传递是最重要的方法。
在使用这种方式传递参数时,特别要注意堆栈的使用情况,一是子程序返回地址的保护与恢复要占用堆栈(因为是计算机自动实现的,特别容易疏忽);二是要保持堆栈的平衡,可以通过使用 RET
n指令来完成。
4.4.3 扩展过程定义(扩展子程序)伪指令
在前面我们给出的子程序定义格式是一个最基本的、最简单约定义格式。在宏汇编 MASM 6.x系统中又提供了带参数的子程序定义方式。
扩展过程定义(扩展子程序)伪指令的一般格式子程序名 PROC [调用距离 ] [语言类型 ] [作用范围 ] [<
起始参数 >] [USES寄存器列表 ] [,参数 [:数据类型 ]]…
LOCAL 定义局部变量
… ;子程序定义体
RET
子程序名 ENDP
子程序名:应该是遵循相应语言类型的标识符。
调用距离:子程序的调用距离有,Near、
Far,Near16,Farl6,Near32和 Far32。
语言类型,子程序语言类型可以是任何一种有效的语言类型,由它来告诉汇编程序将使用什么样的标识符命名约定、
子程序的调用和返回约定。
作用范围:子程序的作用范围决定该子程序对其他模块是否可用。
起始参数:采用这种格式的 PROC伪指令,
汇编程序在处理子程序时能自动产生
,起始,代码 (Prologue code )和,结束,代码 (Epilogue code)。
寄存器列表:保护寄存器说明子句的说明格式为,USES 寄存器列表。
参数:类型子程序参数表示该子程序使用的参数及类型,若有多个参数,则参数之间要用逗号分开。
局部变量的定义
局部变量的定义格式:
LOCAL 变量名 [个数 ][:数据类型 ][,变量名 [个数 ][:数据类型 ][,… ]
伪指令 LOCAL的作用是说明一个或多个临时的局部变量 (位于堆栈区 )。该伪指令必须在任何指令之前加以说明,并可用多个 LOCAL伪指令来说明其局部变量。
子程序声明伪指令
子程序声明伪指令的格式如下:
子程序名 PROTO [调用距离 ] [语言类型 ]
[,参数 [:数据类型 ]]…
该伪指令告诉汇编程序该子程序的若干属性,如调用距离、语言类型、参数个数及其类型等。这样,汇编程序就可以对其定义进行适当的检查。
子程序的 INVOKE调用伪指令
调用伪指令 INCOKEE的一般格式为:
INVOKE 子程序名 [,参数 1,参数
2,… ]
该伪指令是调用基于堆栈的子程序方法,
它把所有参数压栈,子程序结束时,又实现堆栈的平衡。
5.5 中断
在计算机系统中,引入中断的最初目的是为了提高系统的输入输出性能。随着计算机应用的发展,中断技术也应用到计算机系统的许多领域,如:多道程序、
分时系统、实时处理、程序监视和跟踪等领域。
所谓中断就是 CPU暂停当前程序的执行,
转而执行处理紧急事务的程序,并在该事务处理完后能自动恢复执行原先程序的过程。在此,称引起紧急事务的事件为中断源,称处理紧急事务的程序为中断服务程序或中断处理程序。 计算机系统还根据紧急事务的紧急程度,把中断分为不同的优先级,并规定:高优先级的中断能暂停低优先级的中断服务程序的执行。
中断和中断源
计算机系统有上百种可以发出中断请求的中断源,但最常见的中断源是:外设的输入输出请求,如:键盘输入引起的中断,通信端口接受信息引起的中断等;
还有一些计算机内部的异常事件,如,0
作除数、奇偶校验错等。
CPU在执行程序时,是否响应中断要取决于以下三个条件能否同时满足:
(1) 有中断请求;
(2) 允许 CPU接受中断请求;
(3) 一条指令执行完,下一条指令还没有开始执行。
条件 (1)是响应中断的主体。除用指令 INT
所引起的软件中断之外,其它中断请求信号是随机产生的,程序员是无法预见的。
程序员可用程序部分地控制条件 (2)是否满足,即可用指令 STI和 CLI来允许或不允许 CPU
响应可屏蔽的外部中断。而对于不可屏蔽中断和内部中断,CPU一定会响应它们的,程序员是无控制权的。 CPU一定会执行这些中断的中断服务程序。
中断向量表是一个特殊的线性表,
它保存着系统所有中断服务程序的入口地址 (偏移量和段地址 )。在微机系统中,
该向量表有 256个元素 (0~0FFH),每个元素占 4个字节,总共 1K字节,其在内存中的存储形式及其存储内容中断向量表和中断服务程序中断和子程序的比较
中断和子程序调用之间有其相似和不同之处。
它们的工作过程非常相似,即:暂停当前程序的执行,转而执行另一程序段,
当该程序段执行完时,CPU都自动恢复原程序的执行
子程序调用一定是程序员在编写源程序时事先安排好的,是可知的,而中断是由中断源根据自身的需要产生的,是不可预见的 (用指令 INT引起的中断除外 );
子程序调用是用 CALL指令来实现的,但没有调用中断的指令,只有发出中断请求的事件 (指令 INT是发出内部中断信号,而不要理解为调用中断服务程序 );
主要差异
子程序的返回指令是 RET,而中断服务程序的返回指令是 IRET/IRETD。
在通常情况下,子程序是由应用系统的开发者编写的,而中断服务程序是由系统软件设计者编写的。
中断功能的分类
计算机系统有上百种中断,若按中断的性质来划分,则系统中的中断可分为:
可屏蔽中断和不可屏蔽中断。对不可屏蔽中断,程序员不能控制它,系统肯定会立即响应的,而对于可屏蔽中断,汇编语言程序员可以通过指令 CLI和 STI来控制对它们的响应。
若按中断源来划分,则系统中的中断又可分为:硬件中断和软件中断。对于硬件中断,程序员不能控制它,它们基本上是随机产生的,而对于软件中断,汇编语言程序员可通过指令 INT和 INTO来有目的安排它们的。
汇编语言程序员能控制的软件中断
常用的这类中断有,DOS功能调用 (INT
21H),BIOS中断、硬件和外设的中断等。
中断优先级( Interrupt Priority)
在 PC机系统中,有内中断,外中断等多个中断源,因此,可能会出现在同一时刻若干个中断源同时向 CPU发出中断请求,
这时 CPU应如何处理呢? 这就牵涉到优先级的问题 。 所谓优先级,即在处理中断时,按照事件的轻重缓急的程度所确定的先后处理次序 。
中断服务程序 采用过程定义伪指令定义,开始通常要执行 STI指令开放可屏蔽中断,最后执行 IRET指令返回 。
内部中断服务程序 通常采用寄存器传递参数 。
主程序 通过中断调用指令 INT n执行内部中断服务程序 。
主程序在调用内部中断服务程序之前,
必须在中断向量表中设置中断服务程序的入口地址 。
中断向量获取;获取中断 80h的原入口地址
mov ax,3580h
int 21h
mov intoff,bx;保存偏移地址
mov intseg,es;保存段基地址
DOS功能调用 INT 21H的获取中断向量功能入口参数,AH= 35H,AL=中断向量号出口参数,ES:BX=段基地址,偏移地址中断向量设置;设置中断 80h的新入口地址
push ds
mov dx,offset newint80h
mov ax,seg newint80h
mov ds,ax
mov ax,2580h
int 21h
pop ds
DOS功能调用 INT 21H的设置中断向量功能入口参数,AH= 25H,AL=中断向量号;
DS:DX=段基地址,偏移地址调用内部中断;调用中断 80h的服务程序
int 80h;恢复中断 80h的原入口地址
mov dx,intoff
mov ax,intseg
mov ds,ax
mov ax,2580h
int 21h
.exit 0 ;返回 DOS
.model small
.stack
.data
intoff dw?
intseg dw?
.code
.startup;先读取 1CH号中断
mov ah,35h
mov al,1ch
int 21h;保存原中断向量
mov intoff,bx ;保存偏移地址
mov intseg,es ;保存段基地址
push ds;将自己编写的中断的地址写入中断向量表中
mov dx,offset newint1ch
mov ax,seg newint1ch
mov ds,ax
mov ah,25h
mov al,1ch
int 21h
pop ds;调用自己写的中断
int 1ch;恢复原来的中断向量
mov dx,intoff
mov ax,intseg
mov ds,ax
mov ah,25h
mov al,1ch
int 21h
.exit 0
newint1ch proc ;自己编写的中断,显示 "这是我的中断 "
sti
push ax
push bx
push cx
push si
mov si,offset intmsg
mov cx,sizeof intmsg
disp,mov al,cs:[si]
mov bx,0
mov ah,0e
int 10h
inc si
loop disp
pop si
pop cx
pop bx
pop ax
iret
intmsg db '这是我的中断 ',0dh,0ah,'$'
newint1ch endp
end
4.6 输入输出程序
输入输出原理
1,端口与 I/O
外部设备通过 I/O接口与系统总线连接,
每个接口中都有若干称为输入输出端口的寄存器,不同于 AX这样的寄存器,它们是以数字命名的。 CPU就是通过这些寄存器,即端口与外部设备进行通信的,
原理如图
CPU经过端口不仅能传送数据,还能发送控制信息给接口或外部设备、读取接口或外部设备的状态。这里,传送的数据是 CPU发往外设或外设发往 CPU的数据,
控制信息指控制外部设备选通停启的命令,外设状态信息是描述外部设备的忙闲好坏状态的信息。
为了便于 CPU向端口读写数据,对 I/O端口进行编址。
在 IBM-PC机中,I/O端口地址采用独立编制方式,I/O
空间的范围是 64KB。下面我们列出 PC机部分端口的地址。
● 00H~0FH,DMAC的端口地址。
● 20H~21H:可编程中断控制器 PIC,ICR为 20H,IMR为
21H。
● 40H~43H:时钟 /定时器的端口地址。
● 60H:键盘输入端口寄存器地址。
● 61H:设备控制寄存器端口地址。
● 378H~37AH,LPT1的数据、状态、控制端口地址。
● 278H~27AH,LPT2的数据、状态、控制端口地址。
● 3F8H~3FFH,COM1,数据、状态寄存器的端口地址
03F8H。
● 2F8H~2FFH,COM2,数据、状态寄存器的端口地址
02F8H。
CPU与外设间的数据传送方式
无条件传送方式
程序查询传送方式
中断传送方式
直接存储器存取方式无条件传送方式
也叫直接控制输入输出。它不需要查询外设状态,直接使用 I/O指令与外设传送数据。但仅当外部过程与 CPU同步时才可使用这种方式,否则就很难保证在 CPU执行输入输出操作时外设是准备好的。对于工作速度慢,接口电路较简单的外设例如扬声器,适合采用这种方式。
程序查询传送方式
它是利用程序来不断地测试外设的状态,
根据状态决定是否实现输入输出操作的信息的传送方式。例如对于打印机,就可以采用查询方式。 CPU查询打印机是否处于就绪 (Ready)状态,如果就绪,将要打印的字符首先提供给数据端口,然后查询状态端口。如果打印机能接受数据,
则利用控制端口将数据提供给打印机。
TRANS,
IN AL,STATUS_PORT ;从状态端口输入状态信息
TEST AL,80H ;检查 BUSY位(即 D7)
JNZ TRANS ; D7=0表示外设忙,等待
MOV AL,STORE ;外设空闲,即取数据
OUT DATA_PORT,AL ;数据送数据端口中断传送方式
当 CPU需要与外设交换数据时,执行一条指令去完成外设工作,然后继续执行自己的程序,
这就是中断传送方式。输入时若外设的输入数据已送入数据寄存器,在输出时若外设已把一个数据输出,则由外设向 CPU发出中断请求。
当 CPU响应中断时,就暂停正在执行的程序
(即实现中断),转去执行输入或输出操作
(中断服务),一次数据传送完成后,则返回到刚才暂停处(断点地址),CPU继续执行原来程序。
直接存储器存取方式
直接存储器存取( DMA,Direct Memery
Access)是利用专门的硬件电路( DMAC:
DMA控制器),让外设接口可直接与内存进行 高速数据传送,而不必经过 CPU。
I/O程序设计方法
1,无条件传送方式程序设计
例 2:按 0~9不同数字键,发出不同频率声音。
分析:设置扬声器使用 43H端口,设置频率使用 42H端口。频率与 42H端口写入的数据的关系是,数据等于 1.19318*106/发音频率。设
0~9对应的频率为 400Hz+I*50,I为 0到 9的整数,可以用下面的子程序发出不同频率的声音。;发音频率设置子程序;入口参数 AX=1.19318*106/发音频率
SPEAKER PROC
PUSH AX
MOV AL,0B6H ;设置定时器 2工作方式
OUT 43H,AL ; 43H是定时器 2的控制端口
POP AX
OUT 42H,AL ;写入低 8位的定时值
MOV AL,AH
OUT 42H,AL ;写入高 8位的定时值
RET
SPEAKER ENDP
程序查询传送方式程序设计
例 4,异步通讯口程序的设计
分析:我们采用查询方式设计程序。异步通讯口的数据寄存器的端口地址是
03F8H;状态寄存器的端口地址是 03FDH,
其中 D0位是输入数据准备位,D5位是输出数据准备位。
COMMIN PROC NEAR
PUSH DX
PUSH AX
MOV DX,03FDH
WAITIN:
IN AL,DX
TEST AL,01H
JZ WAITIN
MOV DX,03F8H
IN AL,DX
POP AX
POP DX
RET
COMMIN ENDP
异步通讯口输出子程序为:;入口参数,DL中为要发送的字符
COMMOUT PROC NEAR
PUSH DX
PUSH AX
MOV DX,03FDH
WAITOUT,
IN AL,DX
TEST AL,20H
JZ WAITOUT
POP AX
MOV DX,03F8H
OUT DX,AL
POP DX
POP AX
RET
COMMOUT ENDP
4.7 宏结构程序
宏是完成某特定功能的程序段。宏经过定义后,就可以在源程序中多次调用它,
每调用一次,在源程序翻译成目标程序时,就展开一次。宏需要先定义,再调用。
宏汇编
1,宏定义宏指令的定义是使用伪指令 MACRO-ENDM来实现的。宏定义的一般格式为:
宏名 MACRO [形参表 ]
.
,宏定义体
.
ENDM
其中:
MACRO为宏定义的开始伪指令。
宏指令名由程序员自己定义,它的命名规则同变量名,同一源程序中该名字定义唯一。
可选的形式参数表给出了宏定义中用到的形式参数,多个形式参数之间用逗号分隔。
宏定义体完成宏的功能,它由一系列的机器指令语句和伪指令语句组成。
ENDM 为宏定义结束伪指令。它必须和伪指令
MACRO成对出现。
例 1,显示字符串的宏指令分析:在程序中,我们经常需要显示内存缓冲区中的字符串,就需要反复调用 INT 21H的 9号系统功能调用:
……
LEA DX,MESAGE1
MOV AH,9
INT 21H
……
LEA DX,MESAGE2
MOV AH,9
INT 21H
……
LEA DX,MESAGE3
MOV AH,9
INT 21H
在三次调用中,语句格式完全相同,只是每次内存缓冲区首地址不同。这时我们可以将 INT 21H的 9号系统功能调用的过程定义为一条宏指令,并将缓冲区首地址定义为它的形式参数。其定义的格式为:;显示字符串
DISPSTRING MACRO ADDRESS1 ; ADDRESS1为参数
LEA DX,ADDRESS1
MOV AH,9
INT 21H
ENDM
宏调用
宏指令一旦完成定义以后,就可以在源程序中多次调用。宏调用的一般格式为:
宏指令名 [实在参数表 ]
其中,宏指令名必须和宏定义中的宏名一致,其后的实在参数要和宏定义中的形式参数按位置关系一一对应,多个实参之间用逗号分隔。
宏展开
当源程序被汇编时,汇编程序将对每个宏调用作宏展开。宏展开就是把在源程序中出现宏指令名的地方用宏定义体取代,而且实参按位置对应关系取代宏定义体中的形参。这一过程就称为宏展开。在实参取代形参时,实参和形参是一一对应的,即第一个实参取代第一个形参,
第二个实参取代第二个形参 …… 依次类推。一般说来实参的个数要和形参的个数相等,但汇编程序并不要求它们一定要相等。
注意:
宏展开后即用实参取代形参后,所得到的语句应该是有效的,否则汇编程序将会指示出错。
宏定义中可以有宏调用,只要遵循先定义后调用的原则。
宏定义也允许嵌套,即宏定义体内可以有宏定义,对这样的宏进行调用时需要多次分层展开。
宏定义内也允许递归调用,这种情况需要用到后面介绍的条件汇编指令给出递归出口条件。
宏定义和宏调用中的参数
宏的参数功能强大,既可以无参数,又可以带有一个或多个参数。参数的形式非常灵活,可以是常数、变量、存储单元、指令操作码或它们的一部分,还可以是表达式。
宏参数的连接
在宏定义中,有些形参要夹在字符串中。
为了将这种形参标识出来,需要在这样的形参前面加伪操作符,&”,如果形参后面还跟有字符串,则还应该在形参的后面加伪操作符,&”。这时,宏汇编程序 MASM就会识别出夹在,&”之间的形参,
在用相应的实参替换形参后,仍与原来前、后符号连在一起形成一个完整的字符串。
例 5,定义一条宏指令 shift,把 4条位操作指令
shl/shr/sal/sar统一起来
SHIFT MACRO SNUM,SOPRAND,SOPCODE
PUSH CX
MOV CL,SNUM
S&SOPCODE& SOPRAND,CL
POP CX
ENDM
宏调用示例:
SHIFT 4,AX,HL
SHIFT 2,BX,AR
宏展开以后为:
+ PUSH CX
+ MOV CL,4
+ SHL AX,CL
+ POP CX
+ PUSH CX
+ MOV CL,2
+ SAR BX,CL
+ POP CX
带间隔符的实参
在宏调用中,有时实参是一串带间隔符
(如空格、逗号等)的字符串,这时必须采用一对伪操作符尖括号,<>”将它们括起来。如果字符串中包含,<>”或其它特殊意义的符号,则应该使用转义伪操作符,!,。
例 6,字符串用作宏定义参数
DEFSTRING MACRO INFORMATION
DB '&INFORMATION&',0DH,0AH,'$'
ENDM
宏调用示例:
DEFSTRING <THIS IS A STRING>
宏展开以后为:
+ DB 'THIS IS A STRING',0DH,0AH,'$'
数字参数
在某些情况下,需要以实参符号的值而不是符号本身来替换形参,我们把它称为数字参数的替换。宏操作符,%”用来将其后的表达式(通常是符号常数)转换为它所代表的值,并将此数值的 ASCII
码字符嵌入到宏扩展中。它的一般格式为,%表达式。
其它操作符
!为转义操作符,用于指示其后的一个字符作一般字符,而不含特殊含义。;为宏注释符,用于表示在宏定义中的注释,汇编时将不展开。
宏定义中可以用,,REQ”说明设定不可缺参数,用,,=缺省值,设定参数缺省值。
宏与子程序
相同点:宏指令和子程序都可以用来处理程序中重复使用的程序段,减少源程序的书写量,缩短源程序的长度,使源程序结构简洁、清晰。但它们是完全不同的两个概念,有着本质的区别。
不同点
处理时间不同:宏指令在源程序翻译为目标程序时由宏汇编程序处理的(上机操作的第二步),而子程序调用在目标程序执行时(上机操作的第四步),由
CPU直接执行。
调用方式不同:也就是生成目标程序的方式不一样,
宏调用没有专门的调用指令,宏调用是在汇编时用宏定义体替换宏调用指令,实参替换形参,汇编结束,
宏调用指令将随之消失;宏指令每调用一次就要展开一次,因此,使用宏指令会导致目标程序长,占用内存空间大。而子程序有专门的调用指令 CALL,子程序的末尾有专门的返回指令 RET,每调用一次子程序,
CALL 指令就出现一次,汇编程序生成的只是 CALL指令对应的机器码;无论调用多少次,子程序的目标代码只会出现一次,因此,目标程序短,占用内存空间少。
参数传递方式不同:宏调用采用虚实结合的方法实现参数传递的,简捷直观、灵活多变。而子程序需要利用寄存器、存储单元或堆栈等传递参数,传递方式是由用户编程时具体安排的,
特别在参数较多时,非常麻烦,容易出错。对宏调用来说,参数传递错误通常为语法错误,
在汇编源程序时就会发现,而对子程序来说,
参数传递错误为语义错误,不易排除。
执行速度不同:调用子程序时需要完成主子程序的切换,需要专门的指令传递参数,需要使用堆栈保护现场和恢复现场,因此执行速度慢。而宏指令不存在这些问题,因此执行速度快。
在程序设计中,常常会遇到 某些功能完全相同的程序段 在同一程序的多处或不同程序中出现,
如求一个数的阶乘,比较两数大小,求字符串的长度,数制转换等等。为了节省存贮空间,
减少编制程序的重复劳动,可以将这些多次重复的程序段从整个程序中独立出来,附加一些额外语句,将它编制成一种具有公用性的,独立的程序段 —— 子程序,并通过适当的方法把它与其他程序段链接起来,这种程序设计的方法就称为 子程序设计 。
子程序结构的优点:
可以减小程序的长度,节省了计算机汇编源程序的时间和程序的存储空间。
子程序可以重复使用,使得程序设计时间可以大量节省。
增加了程序的可读性,方便对程序的修改、调试。
子程序是模块化、结构化、自顶向下程序设计的基础
MASM6.x子程序定义的方法
一般过程定义(子程序)伪指令。
子程序在汇编语言中也称为过程
( Procedure),它相当于高级语言 C++
的函数和 VB的过程
扩展过程定义(扩展子程序)伪指令。
一般过程定义(子程序)伪指令
过程定义的一般格式为:
过程名 PROC [NEAR/FAR]
.
,;过程定义体
.
RET
过程名 ENDP
过程名由程序员来命名,命名方法同变量名,
同一源程序中不能有相同的过程名。
PROC为过程定义开始伪指令,ENDP为过程定义结束伪指令,PROC-ENDP必须配对使用;配对的 PROC-ENDP前面的过程名应相同。
NEAR/FAR定义了过程的属性,NEAR属性的过程只能被相同代码段的程序调用,称为段内近调用; FAR 属性的过程只能被相同或不同代码段的程序调用,称为段间远调用。
子程序的结构形式
一个完整的子程序一般包括下列内容:
(1) 子程序的说明部分
(2) 子程序的调用和返回
(3) 子程序的现场保护和现场恢复子程序的说明部分
子程序的说明部分一般包括如下内容,
(1)子程序名。命名时要见名知意。
(2)子程序的功能。说明子程序完成的任务。
(3)子程序的入口参数。说明子程序运行所需参数及存放位置。
(4)子程序的出口参数。说明子程序的运行结果的参数及存放位置。
(5)子程序所占用的寄存器和工作单元。
(6)子程序调用示例。
举例;子程序名,B16TOD;子程序的功能:完成将一个 16位二进制数(无符号数)转换为对应的十进制数的; ASCII码,存放在共享存储区 SHALLUNIT;子程序的入口参数:存放在 BINNUM存储单元;子程序的出口参数:存放在共享存储区
SHALLUNIT和共享存储单元 ADDRESS;子程序所占用的寄存器和,AX,DX,SI,BX;子程序调用示例:
子程序的调用和返回
.
.
.
.
.
.
.
.
.
.
.
.
retret
c a l l s u b 2c a l l s u b 1
.
.
.
.
.
.
main
sub1 sub2
end
返回地址
CALL指令的执行分成两步,
第一步,保护返回地址( CALL指令下一条指令的地址),利用堆栈实现,即将返回地址压入堆栈;
第二步,转向子程序,即把子程序的首地址送入 IP或 CS:IP
RET指令的功能是返回主程序,即把子程序的返回地址送入 IP或 CS:IP。
子程序的现场保护和现场恢复
在主程序调用子程序时已经占用了一定数量的寄存器,子程序执行时又要使用寄存器,子程序执行完返回主程序后,
又要保证主程序按原来状态继续执行,
这就需要对那些在主程序中使用过了的寄存器,在子程序中又要用,且它们在主程序中的值又不能被覆盖时(因为子程序返回后主程序还要使用),这些寄存器的内容在子程序体执行之前必须加以保护,这就称为现场保护
子程序执行完再恢复这些寄存器的内容,
称为现场恢复。子程序的现场保护和现场恢复既可以在主程序中完成,也可以在子程序中完成,但为了使程序结构清晰,我们一般在子程序中完成现场保护和现场恢复。
子程序的现场保护和现场恢复方法有两种利用堆栈实现现场保护和现场恢复(利用 PUSH/POP指令实现)。
利用内存单元实现现场保护和现场恢复
(利用 MOV指令实现)。
利用堆栈实现现场保护和恢复过程名 PROC [NEAR/FAR]
PUSH AX.
.
,;保护现场
.
PUSH DX
.
,;过程定义体
.
POP DX
.
,;恢复现场
.
POP AX
RET
过程名 ENDP
4.4.2 子程序参数传递方法
主程序在调用子程序之前,必须把要子程序处理的数据传送给子程序,这些加工处理的数据就称为子程序的输入参数,
也叫入口参数。
当子程序执行完返回主程序时,应该把本次处理的结果返回给主程序,这些加工处理的结果就称为输出参数,也称为子程序的出口参数。
子程序参数传递的方法
利用寄存器实现参数传递。
利用内存单元实现参数传递。
利用堆栈实现参数传递利用寄存器实现子程序参数传递
这种方法是通过寄存器存放参数来进行传递的,即在主程序调用子程序前,将入口参数送到约定寄存器中;子程序可以直接从这些寄存器中取出参数进行加工处理,并将结果也放在约定的寄存器中,然后返回主程序,主程序再从寄存器中取出结果。
利用存贮单元实现子程序参数传递
这种方法是在主程序调用子程序前,将入口参数存放到约定的存储单元(如变量)中,
子程序运行时到约定的位置读取参数;子程序执行结束将结果也放在约定单元。我们还可以通过用寄存器存放存储区首地址,来实现多参数情况下的传递。
利用堆栈实现子程序参数传递
利用堆栈实现参数传递的方法是在调用子程序之前,用 PUSH指令将子程序的入口参数压入堆栈,在子程序中通过出栈指令依次得到这些参数。经过子程序操作处理后再将子程序的出口参数压入堆栈,返回主程序后再通过出栈指令获得结果。一般只通过堆栈传入口参数。
利用堆栈实现参数传递是最重要的方法。
在使用这种方式传递参数时,特别要注意堆栈的使用情况,一是子程序返回地址的保护与恢复要占用堆栈(因为是计算机自动实现的,特别容易疏忽);二是要保持堆栈的平衡,可以通过使用 RET
n指令来完成。
4.4.3 扩展过程定义(扩展子程序)伪指令
在前面我们给出的子程序定义格式是一个最基本的、最简单约定义格式。在宏汇编 MASM 6.x系统中又提供了带参数的子程序定义方式。
扩展过程定义(扩展子程序)伪指令的一般格式子程序名 PROC [调用距离 ] [语言类型 ] [作用范围 ] [<
起始参数 >] [USES寄存器列表 ] [,参数 [:数据类型 ]]…
LOCAL 定义局部变量
… ;子程序定义体
RET
子程序名 ENDP
子程序名:应该是遵循相应语言类型的标识符。
调用距离:子程序的调用距离有,Near、
Far,Near16,Farl6,Near32和 Far32。
语言类型,子程序语言类型可以是任何一种有效的语言类型,由它来告诉汇编程序将使用什么样的标识符命名约定、
子程序的调用和返回约定。
作用范围:子程序的作用范围决定该子程序对其他模块是否可用。
起始参数:采用这种格式的 PROC伪指令,
汇编程序在处理子程序时能自动产生
,起始,代码 (Prologue code )和,结束,代码 (Epilogue code)。
寄存器列表:保护寄存器说明子句的说明格式为,USES 寄存器列表。
参数:类型子程序参数表示该子程序使用的参数及类型,若有多个参数,则参数之间要用逗号分开。
局部变量的定义
局部变量的定义格式:
LOCAL 变量名 [个数 ][:数据类型 ][,变量名 [个数 ][:数据类型 ][,… ]
伪指令 LOCAL的作用是说明一个或多个临时的局部变量 (位于堆栈区 )。该伪指令必须在任何指令之前加以说明,并可用多个 LOCAL伪指令来说明其局部变量。
子程序声明伪指令
子程序声明伪指令的格式如下:
子程序名 PROTO [调用距离 ] [语言类型 ]
[,参数 [:数据类型 ]]…
该伪指令告诉汇编程序该子程序的若干属性,如调用距离、语言类型、参数个数及其类型等。这样,汇编程序就可以对其定义进行适当的检查。
子程序的 INVOKE调用伪指令
调用伪指令 INCOKEE的一般格式为:
INVOKE 子程序名 [,参数 1,参数
2,… ]
该伪指令是调用基于堆栈的子程序方法,
它把所有参数压栈,子程序结束时,又实现堆栈的平衡。
5.5 中断
在计算机系统中,引入中断的最初目的是为了提高系统的输入输出性能。随着计算机应用的发展,中断技术也应用到计算机系统的许多领域,如:多道程序、
分时系统、实时处理、程序监视和跟踪等领域。
所谓中断就是 CPU暂停当前程序的执行,
转而执行处理紧急事务的程序,并在该事务处理完后能自动恢复执行原先程序的过程。在此,称引起紧急事务的事件为中断源,称处理紧急事务的程序为中断服务程序或中断处理程序。 计算机系统还根据紧急事务的紧急程度,把中断分为不同的优先级,并规定:高优先级的中断能暂停低优先级的中断服务程序的执行。
中断和中断源
计算机系统有上百种可以发出中断请求的中断源,但最常见的中断源是:外设的输入输出请求,如:键盘输入引起的中断,通信端口接受信息引起的中断等;
还有一些计算机内部的异常事件,如,0
作除数、奇偶校验错等。
CPU在执行程序时,是否响应中断要取决于以下三个条件能否同时满足:
(1) 有中断请求;
(2) 允许 CPU接受中断请求;
(3) 一条指令执行完,下一条指令还没有开始执行。
条件 (1)是响应中断的主体。除用指令 INT
所引起的软件中断之外,其它中断请求信号是随机产生的,程序员是无法预见的。
程序员可用程序部分地控制条件 (2)是否满足,即可用指令 STI和 CLI来允许或不允许 CPU
响应可屏蔽的外部中断。而对于不可屏蔽中断和内部中断,CPU一定会响应它们的,程序员是无控制权的。 CPU一定会执行这些中断的中断服务程序。
中断向量表是一个特殊的线性表,
它保存着系统所有中断服务程序的入口地址 (偏移量和段地址 )。在微机系统中,
该向量表有 256个元素 (0~0FFH),每个元素占 4个字节,总共 1K字节,其在内存中的存储形式及其存储内容中断向量表和中断服务程序中断和子程序的比较
中断和子程序调用之间有其相似和不同之处。
它们的工作过程非常相似,即:暂停当前程序的执行,转而执行另一程序段,
当该程序段执行完时,CPU都自动恢复原程序的执行
子程序调用一定是程序员在编写源程序时事先安排好的,是可知的,而中断是由中断源根据自身的需要产生的,是不可预见的 (用指令 INT引起的中断除外 );
子程序调用是用 CALL指令来实现的,但没有调用中断的指令,只有发出中断请求的事件 (指令 INT是发出内部中断信号,而不要理解为调用中断服务程序 );
主要差异
子程序的返回指令是 RET,而中断服务程序的返回指令是 IRET/IRETD。
在通常情况下,子程序是由应用系统的开发者编写的,而中断服务程序是由系统软件设计者编写的。
中断功能的分类
计算机系统有上百种中断,若按中断的性质来划分,则系统中的中断可分为:
可屏蔽中断和不可屏蔽中断。对不可屏蔽中断,程序员不能控制它,系统肯定会立即响应的,而对于可屏蔽中断,汇编语言程序员可以通过指令 CLI和 STI来控制对它们的响应。
若按中断源来划分,则系统中的中断又可分为:硬件中断和软件中断。对于硬件中断,程序员不能控制它,它们基本上是随机产生的,而对于软件中断,汇编语言程序员可通过指令 INT和 INTO来有目的安排它们的。
汇编语言程序员能控制的软件中断
常用的这类中断有,DOS功能调用 (INT
21H),BIOS中断、硬件和外设的中断等。
中断优先级( Interrupt Priority)
在 PC机系统中,有内中断,外中断等多个中断源,因此,可能会出现在同一时刻若干个中断源同时向 CPU发出中断请求,
这时 CPU应如何处理呢? 这就牵涉到优先级的问题 。 所谓优先级,即在处理中断时,按照事件的轻重缓急的程度所确定的先后处理次序 。
中断服务程序 采用过程定义伪指令定义,开始通常要执行 STI指令开放可屏蔽中断,最后执行 IRET指令返回 。
内部中断服务程序 通常采用寄存器传递参数 。
主程序 通过中断调用指令 INT n执行内部中断服务程序 。
主程序在调用内部中断服务程序之前,
必须在中断向量表中设置中断服务程序的入口地址 。
中断向量获取;获取中断 80h的原入口地址
mov ax,3580h
int 21h
mov intoff,bx;保存偏移地址
mov intseg,es;保存段基地址
DOS功能调用 INT 21H的获取中断向量功能入口参数,AH= 35H,AL=中断向量号出口参数,ES:BX=段基地址,偏移地址中断向量设置;设置中断 80h的新入口地址
push ds
mov dx,offset newint80h
mov ax,seg newint80h
mov ds,ax
mov ax,2580h
int 21h
pop ds
DOS功能调用 INT 21H的设置中断向量功能入口参数,AH= 25H,AL=中断向量号;
DS:DX=段基地址,偏移地址调用内部中断;调用中断 80h的服务程序
int 80h;恢复中断 80h的原入口地址
mov dx,intoff
mov ax,intseg
mov ds,ax
mov ax,2580h
int 21h
.exit 0 ;返回 DOS
.model small
.stack
.data
intoff dw?
intseg dw?
.code
.startup;先读取 1CH号中断
mov ah,35h
mov al,1ch
int 21h;保存原中断向量
mov intoff,bx ;保存偏移地址
mov intseg,es ;保存段基地址
push ds;将自己编写的中断的地址写入中断向量表中
mov dx,offset newint1ch
mov ax,seg newint1ch
mov ds,ax
mov ah,25h
mov al,1ch
int 21h
pop ds;调用自己写的中断
int 1ch;恢复原来的中断向量
mov dx,intoff
mov ax,intseg
mov ds,ax
mov ah,25h
mov al,1ch
int 21h
.exit 0
newint1ch proc ;自己编写的中断,显示 "这是我的中断 "
sti
push ax
push bx
push cx
push si
mov si,offset intmsg
mov cx,sizeof intmsg
disp,mov al,cs:[si]
mov bx,0
mov ah,0e
int 10h
inc si
loop disp
pop si
pop cx
pop bx
pop ax
iret
intmsg db '这是我的中断 ',0dh,0ah,'$'
newint1ch endp
end
4.6 输入输出程序
输入输出原理
1,端口与 I/O
外部设备通过 I/O接口与系统总线连接,
每个接口中都有若干称为输入输出端口的寄存器,不同于 AX这样的寄存器,它们是以数字命名的。 CPU就是通过这些寄存器,即端口与外部设备进行通信的,
原理如图
CPU经过端口不仅能传送数据,还能发送控制信息给接口或外部设备、读取接口或外部设备的状态。这里,传送的数据是 CPU发往外设或外设发往 CPU的数据,
控制信息指控制外部设备选通停启的命令,外设状态信息是描述外部设备的忙闲好坏状态的信息。
为了便于 CPU向端口读写数据,对 I/O端口进行编址。
在 IBM-PC机中,I/O端口地址采用独立编制方式,I/O
空间的范围是 64KB。下面我们列出 PC机部分端口的地址。
● 00H~0FH,DMAC的端口地址。
● 20H~21H:可编程中断控制器 PIC,ICR为 20H,IMR为
21H。
● 40H~43H:时钟 /定时器的端口地址。
● 60H:键盘输入端口寄存器地址。
● 61H:设备控制寄存器端口地址。
● 378H~37AH,LPT1的数据、状态、控制端口地址。
● 278H~27AH,LPT2的数据、状态、控制端口地址。
● 3F8H~3FFH,COM1,数据、状态寄存器的端口地址
03F8H。
● 2F8H~2FFH,COM2,数据、状态寄存器的端口地址
02F8H。
CPU与外设间的数据传送方式
无条件传送方式
程序查询传送方式
中断传送方式
直接存储器存取方式无条件传送方式
也叫直接控制输入输出。它不需要查询外设状态,直接使用 I/O指令与外设传送数据。但仅当外部过程与 CPU同步时才可使用这种方式,否则就很难保证在 CPU执行输入输出操作时外设是准备好的。对于工作速度慢,接口电路较简单的外设例如扬声器,适合采用这种方式。
程序查询传送方式
它是利用程序来不断地测试外设的状态,
根据状态决定是否实现输入输出操作的信息的传送方式。例如对于打印机,就可以采用查询方式。 CPU查询打印机是否处于就绪 (Ready)状态,如果就绪,将要打印的字符首先提供给数据端口,然后查询状态端口。如果打印机能接受数据,
则利用控制端口将数据提供给打印机。
TRANS,
IN AL,STATUS_PORT ;从状态端口输入状态信息
TEST AL,80H ;检查 BUSY位(即 D7)
JNZ TRANS ; D7=0表示外设忙,等待
MOV AL,STORE ;外设空闲,即取数据
OUT DATA_PORT,AL ;数据送数据端口中断传送方式
当 CPU需要与外设交换数据时,执行一条指令去完成外设工作,然后继续执行自己的程序,
这就是中断传送方式。输入时若外设的输入数据已送入数据寄存器,在输出时若外设已把一个数据输出,则由外设向 CPU发出中断请求。
当 CPU响应中断时,就暂停正在执行的程序
(即实现中断),转去执行输入或输出操作
(中断服务),一次数据传送完成后,则返回到刚才暂停处(断点地址),CPU继续执行原来程序。
直接存储器存取方式
直接存储器存取( DMA,Direct Memery
Access)是利用专门的硬件电路( DMAC:
DMA控制器),让外设接口可直接与内存进行 高速数据传送,而不必经过 CPU。
I/O程序设计方法
1,无条件传送方式程序设计
例 2:按 0~9不同数字键,发出不同频率声音。
分析:设置扬声器使用 43H端口,设置频率使用 42H端口。频率与 42H端口写入的数据的关系是,数据等于 1.19318*106/发音频率。设
0~9对应的频率为 400Hz+I*50,I为 0到 9的整数,可以用下面的子程序发出不同频率的声音。;发音频率设置子程序;入口参数 AX=1.19318*106/发音频率
SPEAKER PROC
PUSH AX
MOV AL,0B6H ;设置定时器 2工作方式
OUT 43H,AL ; 43H是定时器 2的控制端口
POP AX
OUT 42H,AL ;写入低 8位的定时值
MOV AL,AH
OUT 42H,AL ;写入高 8位的定时值
RET
SPEAKER ENDP
程序查询传送方式程序设计
例 4,异步通讯口程序的设计
分析:我们采用查询方式设计程序。异步通讯口的数据寄存器的端口地址是
03F8H;状态寄存器的端口地址是 03FDH,
其中 D0位是输入数据准备位,D5位是输出数据准备位。
COMMIN PROC NEAR
PUSH DX
PUSH AX
MOV DX,03FDH
WAITIN:
IN AL,DX
TEST AL,01H
JZ WAITIN
MOV DX,03F8H
IN AL,DX
POP AX
POP DX
RET
COMMIN ENDP
异步通讯口输出子程序为:;入口参数,DL中为要发送的字符
COMMOUT PROC NEAR
PUSH DX
PUSH AX
MOV DX,03FDH
WAITOUT,
IN AL,DX
TEST AL,20H
JZ WAITOUT
POP AX
MOV DX,03F8H
OUT DX,AL
POP DX
POP AX
RET
COMMOUT ENDP
4.7 宏结构程序
宏是完成某特定功能的程序段。宏经过定义后,就可以在源程序中多次调用它,
每调用一次,在源程序翻译成目标程序时,就展开一次。宏需要先定义,再调用。
宏汇编
1,宏定义宏指令的定义是使用伪指令 MACRO-ENDM来实现的。宏定义的一般格式为:
宏名 MACRO [形参表 ]
.
,宏定义体
.
ENDM
其中:
MACRO为宏定义的开始伪指令。
宏指令名由程序员自己定义,它的命名规则同变量名,同一源程序中该名字定义唯一。
可选的形式参数表给出了宏定义中用到的形式参数,多个形式参数之间用逗号分隔。
宏定义体完成宏的功能,它由一系列的机器指令语句和伪指令语句组成。
ENDM 为宏定义结束伪指令。它必须和伪指令
MACRO成对出现。
例 1,显示字符串的宏指令分析:在程序中,我们经常需要显示内存缓冲区中的字符串,就需要反复调用 INT 21H的 9号系统功能调用:
……
LEA DX,MESAGE1
MOV AH,9
INT 21H
……
LEA DX,MESAGE2
MOV AH,9
INT 21H
……
LEA DX,MESAGE3
MOV AH,9
INT 21H
在三次调用中,语句格式完全相同,只是每次内存缓冲区首地址不同。这时我们可以将 INT 21H的 9号系统功能调用的过程定义为一条宏指令,并将缓冲区首地址定义为它的形式参数。其定义的格式为:;显示字符串
DISPSTRING MACRO ADDRESS1 ; ADDRESS1为参数
LEA DX,ADDRESS1
MOV AH,9
INT 21H
ENDM
宏调用
宏指令一旦完成定义以后,就可以在源程序中多次调用。宏调用的一般格式为:
宏指令名 [实在参数表 ]
其中,宏指令名必须和宏定义中的宏名一致,其后的实在参数要和宏定义中的形式参数按位置关系一一对应,多个实参之间用逗号分隔。
宏展开
当源程序被汇编时,汇编程序将对每个宏调用作宏展开。宏展开就是把在源程序中出现宏指令名的地方用宏定义体取代,而且实参按位置对应关系取代宏定义体中的形参。这一过程就称为宏展开。在实参取代形参时,实参和形参是一一对应的,即第一个实参取代第一个形参,
第二个实参取代第二个形参 …… 依次类推。一般说来实参的个数要和形参的个数相等,但汇编程序并不要求它们一定要相等。
注意:
宏展开后即用实参取代形参后,所得到的语句应该是有效的,否则汇编程序将会指示出错。
宏定义中可以有宏调用,只要遵循先定义后调用的原则。
宏定义也允许嵌套,即宏定义体内可以有宏定义,对这样的宏进行调用时需要多次分层展开。
宏定义内也允许递归调用,这种情况需要用到后面介绍的条件汇编指令给出递归出口条件。
宏定义和宏调用中的参数
宏的参数功能强大,既可以无参数,又可以带有一个或多个参数。参数的形式非常灵活,可以是常数、变量、存储单元、指令操作码或它们的一部分,还可以是表达式。
宏参数的连接
在宏定义中,有些形参要夹在字符串中。
为了将这种形参标识出来,需要在这样的形参前面加伪操作符,&”,如果形参后面还跟有字符串,则还应该在形参的后面加伪操作符,&”。这时,宏汇编程序 MASM就会识别出夹在,&”之间的形参,
在用相应的实参替换形参后,仍与原来前、后符号连在一起形成一个完整的字符串。
例 5,定义一条宏指令 shift,把 4条位操作指令
shl/shr/sal/sar统一起来
SHIFT MACRO SNUM,SOPRAND,SOPCODE
PUSH CX
MOV CL,SNUM
S&SOPCODE& SOPRAND,CL
POP CX
ENDM
宏调用示例:
SHIFT 4,AX,HL
SHIFT 2,BX,AR
宏展开以后为:
+ PUSH CX
+ MOV CL,4
+ SHL AX,CL
+ POP CX
+ PUSH CX
+ MOV CL,2
+ SAR BX,CL
+ POP CX
带间隔符的实参
在宏调用中,有时实参是一串带间隔符
(如空格、逗号等)的字符串,这时必须采用一对伪操作符尖括号,<>”将它们括起来。如果字符串中包含,<>”或其它特殊意义的符号,则应该使用转义伪操作符,!,。
例 6,字符串用作宏定义参数
DEFSTRING MACRO INFORMATION
DB '&INFORMATION&',0DH,0AH,'$'
ENDM
宏调用示例:
DEFSTRING <THIS IS A STRING>
宏展开以后为:
+ DB 'THIS IS A STRING',0DH,0AH,'$'
数字参数
在某些情况下,需要以实参符号的值而不是符号本身来替换形参,我们把它称为数字参数的替换。宏操作符,%”用来将其后的表达式(通常是符号常数)转换为它所代表的值,并将此数值的 ASCII
码字符嵌入到宏扩展中。它的一般格式为,%表达式。
其它操作符
!为转义操作符,用于指示其后的一个字符作一般字符,而不含特殊含义。;为宏注释符,用于表示在宏定义中的注释,汇编时将不展开。
宏定义中可以用,,REQ”说明设定不可缺参数,用,,=缺省值,设定参数缺省值。
宏与子程序
相同点:宏指令和子程序都可以用来处理程序中重复使用的程序段,减少源程序的书写量,缩短源程序的长度,使源程序结构简洁、清晰。但它们是完全不同的两个概念,有着本质的区别。
不同点
处理时间不同:宏指令在源程序翻译为目标程序时由宏汇编程序处理的(上机操作的第二步),而子程序调用在目标程序执行时(上机操作的第四步),由
CPU直接执行。
调用方式不同:也就是生成目标程序的方式不一样,
宏调用没有专门的调用指令,宏调用是在汇编时用宏定义体替换宏调用指令,实参替换形参,汇编结束,
宏调用指令将随之消失;宏指令每调用一次就要展开一次,因此,使用宏指令会导致目标程序长,占用内存空间大。而子程序有专门的调用指令 CALL,子程序的末尾有专门的返回指令 RET,每调用一次子程序,
CALL 指令就出现一次,汇编程序生成的只是 CALL指令对应的机器码;无论调用多少次,子程序的目标代码只会出现一次,因此,目标程序短,占用内存空间少。
参数传递方式不同:宏调用采用虚实结合的方法实现参数传递的,简捷直观、灵活多变。而子程序需要利用寄存器、存储单元或堆栈等传递参数,传递方式是由用户编程时具体安排的,
特别在参数较多时,非常麻烦,容易出错。对宏调用来说,参数传递错误通常为语法错误,
在汇编源程序时就会发现,而对子程序来说,
参数传递错误为语义错误,不易排除。
执行速度不同:调用子程序时需要完成主子程序的切换,需要专门的指令传递参数,需要使用堆栈保护现场和恢复现场,因此执行速度慢。而宏指令不存在这些问题,因此执行速度快。