430 likes | 625 Views
第 4 章 汇编语言程序设计. 4.1 顺序程序设计 4.2 分支程序设计 4.3 循环程序设计 4.4 子程序设计 4.5 宏结构程序设计 4.6 模块化程序设计 4.7 输入输出程序设计. 4.4 子程序设计. 把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序。 子程序可以实现源程序的模块化,可以简化源程序结构,可以提高编程效率。. 子程序设计要利用过程定义伪指令. 参数传递是子程序设计的重点和难点. 子程序可以嵌套; 一定条件下,还可以递归和重入.
E N D
第4章 汇编语言程序设计 4.1 顺序程序设计 4.2 分支程序设计 4.3 循环程序设计 4.4 子程序设计 4.5 宏结构程序设计 4.6 模块化程序设计 4.7 输入输出程序设计
4.4 子程序设计 把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序。 子程序可以实现源程序的模块化,可以简化源程序结构,可以提高编程效率。 子程序设计要利用过程定义伪指令 参数传递是子程序设计的重点和难点 子程序可以嵌套; 一定条件下,还可以递归和重入
过程名proc [near|far] ... 过程名endp 过程名(子程序名)为符合语法的标识符。 NEAR属性(段内近调用)的过程只能被相同代码段的其他程序调用; FAR属性(段间远调用)的过程可以被相同或不同代码段的程序调用。 对简化段定义格式,在微型、小型和紧凑存储模式下,过程的缺省属性为near;在中型、大型和巨型存储模式下,过程的缺省属性为far。对完整段定义格式,过程的缺省属性为near。 用户可以在过程定义时用near或far改变缺省属性。 4.4.1 程序定义伪指令
子程序的常见格式 subname proc ;具有缺省属性的subname过程 push ax ;保护寄存器:顺序压入堆栈 push bx ;ax/bx/cx仅是示例 push cx ... ;过程体 pop cx ;恢复寄存器:逆序弹出堆栈 pop bx pop ax ret ;过程返回 subname endp ;过程结束 例4.10
例4.10上 ALdisp proc ;实现al内容的显示 push ax ;过程中使用了AX、CX和DX push cx push dx push ax ;暂存ax mov dl,al ;转换al的高4位 mov cl,4 shr dl,cl or dl,30h ;al高4位变成3 cmp dl,39h jbe aldisp1 add dl,7 ;是0Ah~0Fh,其ASCII码还要加上7 aldisp1: mov ah,2 ;显示 int 21h
例4.10下 pop dx ;恢复原ax值到dx and dl,0fh ;转换al的低4位 or dl,30h cmp dl,39h jbe aldisp2 add dl,7 aldisp2: mov ah,2 ;显示 int 21h pop dx pop cx pop ax ret ;过程返回 ALdisp endp
操作 例4.10应用 ... ;主程序,同例4.8源程序 loop outlp ;外循环尾 mov bx,offset array ;调用程序段开始 mov cx,count displp: mov al,[bx] call ALdisp ;调用显示过程 mov dl,',' ;显示一个逗号,以分隔两个数据 mov ah,2 int 21h inc bx loop displp ;调用程序段结束 .exit 0 ... ;过程定义,同例4.10源程序 end
入口参数(输入参数):主程序 提供给子程序 出口参数(输出参数):子程序 返回给主程序 参数的形式:① 数据本身(传值) ② 数据的地址(传址) 传递的方法:① 寄存器 ② 变量 ③ 堆栈 例4.11子程序计算数组元素的“校验和” 入口参数: 数组的逻辑地址(传址), 元素个数(传值); 出口参数: 求和结果(传值)。 4.4.2 子程序的参数传递
用寄存器传递参数 把参数存于约定的寄存器中,可以传值,也可以传址。 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护)。 子程序对带有入口参数的寄存器可以保护,也可以不保护;但最好一致。 例4.11a • 入口参数:CX=元素个数, DS:BX=数组的段地址:偏移地址 • 出口参数:AL=校验和
.startup ;设置入口参数(含有DS←数组的段地址).startup ;设置入口参数(含有DS←数组的段地址) mov bx,offset array ;BX←数组的偏移地址 mov cx,count ;CX←数组的元素个数 call checksuma ;调用求和过程 mov result,al ;处理出口参数 .exit 0 checksuma proc xor al,al ;累加器清0 suma: add al,[bx] ;求和 inc bx ;指向下一个字节 loop suma ret checksuma endp end 主程序a 子程序a
用变量传递参数 主程序和子程序直接采用同一个变量名共享同一个变量,实现参数的传递。 不通模块间共享时,需要声明。 例4.11b • 入口参数: count=元素个数, array=数组名(含段地址:偏移地址) • 出口参数: result=校验和
checksumb proc push ax push bx push cx xor al,al ;累加器清0 mov bx,offset array ;BX←数组的偏移地址 mov cx,count ;CX←数组的元素个数 sumb: add al,[bx] ;求和 inc bx loop sumb mov result,al ;保存校验和 pop cx pop bx pop ax ret checksumb endp call checksumb ;调用求和过程 主程序b 子程序b
用堆栈传递参数 主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数; 子程序将出口参数压入堆栈,主程序弹出堆栈取得它们。 例4.11c • 入口参数: 顺序压入偏移地址和元素个数 • 出口参数: AL=校验和
checksumc proc push bp mov bp,sp push bx push cx mov bx,[bp+6] mov cx,[bp+4] xor al,al sumc: add al,[bx] inc bx loop sumc pop cx pop bx pop bp ret checksumc endp mov ax,offset array push ax mov ax,count push ax call checksumc add sp,4 mov result,al 主程序c 子程序c 参见图 • 利用BP间接寻址取出参数 注意位移量 • 主程序实现平衡堆栈: add sp,n • 子程序实现平衡堆栈: ret n
子程序的嵌套 子程序内包含有子程序 的调用就是子程序嵌套。 没有什么特殊要求 例题4.10
ALdisp proc push ax push cx push dx push ax mov dl,al mov cl,4 shr dl,cl call dldisp pop dx and dl,0fh call dldisp pop dx pop cx pop ax ret ALdisp endp ;将dl中的一位16进制数显示出来 dldisp proc or dl,30h cmp dl,39h jbe dldisp1 add dl,7 dldisp1: mov ah,2 int 21h ret dldisp endp 例4.10:子程序嵌套
子程序的递归 当子程序直接或间接地嵌套调用自身时称为递归调用,含有递归调用的子程序称为递归子程序。 递归子程序必须采用寄存器或堆栈传递参数,递归深度受堆栈空间的限制。 例4.12:求阶乘
例4.12主程序 .model small .stack 256 .data N dw 3 result dw ? .code .startup mov bx,N push bx call fact pop result .exit 0
递归子程序 fact proc ;计算N!的近过程 push ax ;入口参数:压入N push bp ;出口参数:弹出N! mov bp,sp mov ax,[bp+6] cmp ax,0 jne fact1 inc ax jmp fact2 fact1: dec ax push ax call fact pop ax mul word ptr [bp+6] fact2: mov [bp+6],ax pop bp pop ax ret fact endp
子程序的重入 • 子程序的重入是指子程序被中断后又被中断服务程序所调用,能够重入的子程序称为可重入子程序。在子程序中,注意利用寄存器和堆栈传递参数和存放临时数据,而不要使用固定的存储单元(变量),就能够实现重入。 • 子程序的重入不同于子程序的递归。重入是被动地进入,而递归是主动地进入;重入的调用间往往没有关系,而递归的调用间却是密切相关的。递归子程序也是可重入子程序。
4.5 宏结构程序设计 宏汇编 重复汇编 条件汇编 ——统称宏结构 宏(Macro)是汇编语言的一个特点,它是与子程序类似又独具特色的另一种简化源程序的方法。
4.5.1 宏汇编 宏——具有宏名的一段汇编语句序列, ——宏定义时书写 宏指令——这段汇编语句序列的缩写, ——宏调用时书写 宏展开——在宏指令处用这段宏代替的过程, ——宏汇编时实现 宏的参数功能强大,颇具特色。 配合宏,还有宏操作符和有关伪指令。 宏与子程序实际上具有本质的区别。
宏名macro [形参表] 宏定义体 endm mainbegin MACRO ;;定义一个名为mainbegin的宏,无参数 mov ax,@data ;;宏定义体 mov ds,ax ENDM ;;宏定义结束 mainend MACRO retnum ;;带有形参retnum mov al,retnum ;;宏定义中使用参数 mov ah,4ch int 21h ENDM 宏定义
宏名[实参表] start: mainbegin ;宏调用,建立DS内容 dispmsg string ;宏调用,显示字符串 mainend 0 ;宏调用,返回DOS end start 宏调用的实质是在汇编过程中进行宏展开。 宏展开的具体过程是:当汇编程序扫描源程序遇到已有定义的宏调用时,即用相应的宏定义体取代源程序的宏指令,同时用位置匹配的实参对形参进行取代。 宏调用
宏展开——在汇编时,用宏定义体的代码序列替代宏指令的过程。宏展开——在汇编时,用宏定义体的代码序列替代宏指令的过程。 start: mainbegin ;宏指令 1 mov ax,@data ;宏展开 1 mov ds,ax mainend 0 ;宏指令 1 mov al,0 ;宏展开 1 mov ah,4ch 1 int 21h 例4.13b操作 宏展开
宏的参数使用非常灵活 宏定义时, 可以无参数,例如4.13a的mainbegin; 可以带有一个参数,例如4.13a的mainend; 也可以具有多个参数;例如4.14a的shlext。 参数可以是常数、变量、存储单元、指令(操作码)或它们的一部分,也可以是表达式;例如4.14b的shift和shrot。 宏定义体可以是任何合法的汇编语句,既可以是硬指令序列,又可以是伪指令序列;例如4.15的dstring 。 宏的参数
例4.14a ;宏定义 shlext macro shloprand,shlnum push cx mov cl,shlnum shl shloprand,cl pop cx endm ;宏指令 shlext ax,6 ;宏展开 1 push cx 1 mov cl,06 1 shl ax,cl 1 pop cx
例4.14b ;统一4条移位指令的宏指令 shift macro soprand,snum,sopcode push cx mov cl,snum s&sopcode& soprand,cl pop cx endm ;统一移位和循环移位8条指令的宏指令 shrot macro sroprand,srnum,sropcode push cx mov cl,srnum sropcode sroprand,cl pop cx endm
例4.15 ;宏定义 dstring macro string db '&string&',0dh,0ah,'$' endm ;宏调用 dstring < This is a example. > dstring < 0 !< Number !< 10 > ;宏展开 1 db 'This is a example.', 0dh,0ah,'$’ 1 db '0 < Number < 10', 0dh,0ah,'$'
&——替换操作符,用于将参数与其他字符分开。如果参数紧接在其他字符之前或之后,或者参数出现在带引号的字符串中,就必须使用该伪操作符。&——替换操作符,用于将参数与其他字符分开。如果参数紧接在其他字符之前或之后,或者参数出现在带引号的字符串中,就必须使用该伪操作符。 < >——字符串传递操作符,用于括起字符串。在宏调用中,如果传递的字符串实参数含有逗号、空格等间隔符号,则必须用这对操作符,以保证字符串的完整。 !——转义操作符,用于指示其后的一个字符作为一般字符,而不含特殊意义。 %——表达式操作符,用在宏调用中,表示将后跟的一个表达式的值作为实参,而不是将表达式本身作为参数 ;;——宏注释符,用于表示在宏定义中的注释。采用这个符号的注释,在宏展开时不出现。 宏操作符
局部标号伪指令 LOCAL 标号列表 宏定义体采用了标号,应使用局部标号伪指令LOCAL加以说明。它必须是宏定义MACRO语句之后的第一条语句。 宏定义删除伪指令 PURGE 宏名表 不需要某个宏定义时,可以把它删除。 宏定义退出伪指令 EXITM 伪指令EXITM表示结束当前宏调用的展开。 与宏有关的伪指令
例4.16 ;宏调用 absol word ptr [bx] absol bx ;宏展开 1 cmp word ptr [bx],0 1 jge ??0000 1 neg word ptr [bx] 1 ??0000: 1 cmp bx,0 1 jge ??0001 1 neg bx 1 ??0001: ;宏定义 absol macro oprd local next cmp oprd,0 jge next neg oprd next: endm
宏与子程序具有各自的特点,程序员应该根据具体问题选择使用那种方法。宏与子程序具有各自的特点,程序员应该根据具体问题选择使用那种方法。 通常,当程序段较短或要求较快执行时,应选用宏;当程序段较长或为减小目标代码时,要选用子程序。 宏——仅是源程序级的简化:宏调用在汇编时进行程序语句的展开,不需要返回;不减小目标程序,执行速度没有改变。 宏调用的参数通过形参、实参结合实现传递,简捷直观、灵活多变。 子程序——还是目标程序级的简化:子程序调用在执行时由CALL指令转向,RET指令返回;形成的目标代码较短,执行速度减慢。 子程序需要利用寄存器、存储单元或堆栈等传递参数。 宏与子程序的比较
4.5.2 重复汇编 重复汇编指在汇编过程中,重复展开一段(基本)相同的语句。 重复汇编没有名字,不能被调用; 重复汇编常用在宏定义体中,也可以在一般汇编语句中使用。 重复汇编伪指令有三个: • REPEAT——按参数值重复, • FOR——按参数个数重复, • FORC——按参数的字符个数重复; 最后,用ENDM结束。
按参数值重复 REPEAT重复次数 重复体 ENDM char = 'A' REPEAT 26 db char char = char +1 ENDM 1 db char ;等效于db 'A' 1 char = char +1 1 db char ;等效于db 'B' 1 char = char +1 ... 1 db char ;等效于db 'Z' 1 char = char +1
按参数个数重复 FOR形参,〈实参表〉 重复体 ENDM FOR regad, <ax,bx,cx,dx> push regad ENDM 1 push ax 1 push bx 1 push cx 1 push dx
按参数字符个数重复 FORC 形参, 字符串 重复体 ENDM FORC regad,dcba pop ®ad&x ENDM 1 pop dx 1 pop cx 1 pop bx 1 pop ax
4.5.3 条件汇编 条件汇编伪指令在汇编过程中,根据条件决定汇编的语句。 IFxx 表达式;条件满足,汇编分支语句体1 分支语句体1 [ ELSE ;条件不满足,汇编分支语句体2 汇编分支语句体2 ] ENDIF ;条件汇编结束 例4.13c 例4.19
例4.19 pdata macro num IF num lt 100 ;;如果num < 100,则汇编如下语句 db num dup (?) ELSE ;;否则,汇编如下语句 db 100 dup (?) ENDIF endm pdata 12 ;宏调用① db 12 dup(?) ;宏汇编结果① pdata 102 ;宏调用② db 100 dup(?) ;宏汇编结果②
例4.13c上 dstring MACRO string ;;定义字符串 db '&string&',0dh,0ah,'$' ENDM mainbegin MACRO dsseg ;;设置数据段地址 mov ax,dsseg mov ds,ax ENDM dispmsg MACRO message mov dx,offset message mov ah,09h int 21h ENDM
例4.13c中 mainend MACRO retnum ;;返回DOS,可以不带参数 ifb <retnum> mov ah,4ch ;;没有参数 else mov ax,4c00h+(retnum AND 0ffh) ;; 有参数 endif int 21h ENDM
例4.13c下 .model small .stack 256 .data msg1 equ this byte dstring <Hello,Everybody !!> msg2 equ this byte dstring <You see,I made it.> .code start: mainbegin @data ;建立DS内容 dispmsg msg1 ;显示msg1字符串 dispmsg msg2 ;显示msg2字符串 mainend ;返回DOS end start
宏结构的作用 宏汇编、重复汇编和条件汇编 为源程序的编写提供了很多方便, 灵活运用它们可以编写出非常良好 的源程序来。 汇编系统中有些以圆点起始的 伪指令(如.startup、.exit等) 实际上是一种宏结构。