第六章 子程序结构 §6.1 子程序的设计方法 §6.2 嵌套与递归子程序 §6.3 子程序举例 §6.4 DOS系统功能调用
§6.1 子程序的设计方法 一、子程序指令 二、子程序的调用与返回 三、现场的保护与恢复 四、子程序参数的传递
一、子程序指令 子程序是完成特定功能的一段程序 当主程序(调用程序)需要执行这个功能时,采用CALL调用指令转移到该子程序的起始处执行 当运行完子程序功能后,采用RET返回指令回到主程序继续执行
一、子程序指令—调用指令 CALL指令分成4种类型(类似JMP) CALL指令需要保存返回地址: CALL label ;段内调用、直接寻址 CALL r16/m16 ;段内调用、间接寻址 CALL far ptr label ;段间调用、直接寻址 CALL far ptr mem ;段间调用、间接寻址 CALL指令需要保存返回地址: 段内调用——入栈偏移地址IP SP←SP-2,SS:[SP]←IP 段间调用——入栈偏移地址IP和段地址CS SP←SP-2,SS:[SP]←CS
一、子程序指令—返回指令 根据段内和段间、有无参数,分成4种类型 需要弹出CALL指令压入堆栈的返回地址 RET ;无参数段内返回 RET i16 ;有参数段内返回 RET ;无参数段间返回 RET i16 ;有参数段间返回 需要弹出CALL指令压入堆栈的返回地址 段内返回——出栈偏移地址IP IP←SS:[SP], SP←SP+2 段间返回——出栈偏移地址IP和段地址CS IP←SS:[SP],SP←SP+2 CS←SS:[SP],SP←SP+2
一、子程序指令—返回指令RET的参数 RET i16 ;有参数返回 RET指令可以带有一个立即数i16,完成RET操作后,堆栈指针SP将增加,即 SP←SP+i16 这个特点使得程序可以方便地废除若干执行CALL指令以前入栈的参数
二、子程序的调用与返回 CALL label 主程序 RET 子程序 回到CALL指令后的指令处——返回地址
二、子程序的调用与返回 —书写形式(同一代码段内) 二、子程序的调用与返回 —书写形式(同一代码段内)
二、子程序的调用与返回 —书写形式(不同代码段) 二、子程序的调用与返回 —书写形式(不同代码段)
三、现场的保护与恢复 现场:主程序转向子程序之前,其所使用的一些资源的状态(如标志位、R/M等) 子程序与主程序分别编制,通常会导致使用的资源发生冲突而影响主程序在调用子程序之后的正确执行 方法:利用堆栈 在主程序中进行 在子程序中进行
三、现场的保护与恢复 —在主程序中进行 …… PUSH BX 注意: 进栈/出栈的顺序 PUSH AX CALL SUB1 POP AX 三、现场的保护与恢复 —在主程序中进行 …… PUSH BX PUSH AX CALL SUB1 POP AX POP BX 注意: 进栈/出栈的顺序 保护与恢复的对象: 主程序用到的存有数据、中间结果且在CALL指令后还要用到的R/M
三、现场的保护与恢复 —在子程序中进行 SUB1 PROC PUSH BX 注意: 进栈/出栈的顺序 PUSH AX …… POP AX 三、现场的保护与恢复 —在子程序中进行 SUB1 PROC PUSH BX PUSH AX …… POP AX POP BX RET SUB1 ENDP 注意: 进栈/出栈的顺序 保护与恢复的对象: 子程序用到的R/M
四、子程序参数的传递 入口参数(输入参数):主程序提供给子程序 出口参数(输出参数):子程序返回给主程序 参数的形式: 传递的方法: ① 数据本身(传值) ② 数据的地址(传址) 传递的方法: ① 寄存器 ② 变量 ③ 堆栈
四、子程序参数的传递 通过寄存器传送参数 例6.3十进制到到十六进制转换程序。程序要求从键盘取得一个十进制数,然后把该数以十六进制的形式显示出来。
四、子程序参数的传递 Decihex ends end main Decihex segment assume cs:decihex Main proc far Repeat :call decibin call crlf call binihex jmp repeat Main endp … Decihex ends end main
四、子程序参数的传递 Decibin proc near mov bx,0 newchar: mov ah,1 int 21h sub al,30h jl exit cmp al,9d jg exit cbw xchg ax,bx mov cx,10d mul cx add bx,ax jmp newchar Exit: ret Decibin endp
四、子程序参数的传递 Binhex proc near mov ch,4 rotate: mov cl,4 rol bx,cl mov al,bl and al,0fh add al,30h cmp al,3ah jl printit add al,7h printit: mov dl,al mov ah,2 int 21h dec ch jnz rotate ret binihex endp
四、子程序参数的传递 Crlf proc near mov dl,0dh mov ah,2 int 21h mov dl,0ah ret Crlf endp
四、子程序参数的传递 如过程和调用程序在同一源文件中,则过程可直接访问模块中的变量 例6.4主程序MAIN和子程序PROADD在同一源文件中,要求用子程序PROADD累加数组中的所有元素,并把和(不考虑溢出的可能性)送到指定的存储单元中去。在这里,子程序PROADD直接访问模块的数据区。
四、子程序参数的传递 多个模块之间的参数传递 PUBLIC symbol[,……] 在一个模块中定义的符号(包括变量、标号、过程名等)在提供给其它模块使用时,必须要使用PUBLIC定义该符号为外部符号。 EXTRN symbol name:type[,……] 在另一个模块中定义而要在本模块中使用的符号必须使用EXTRN伪操作,如符号为变量,则类型应该为byte,word,dword等;如符号为标号或过程名,则类型应为near,far
四、子程序参数的传递 例6.5 …….. Extrn var:word,lab2:far Public var1,var4,lab1 Data1 segment var1 db ? var3 dw ? var4 dw ? Data1 ends Code1 segment assume cs:code1,ds:data1 Main proc far Start: mov ax,data1 mov ds,ax …….. Lab1: mov ax,4c00h int 21h Main endp Code ends end start
四、子程序参数的传递 Extrn var1:byte,var4:word Extrn lab1:far Public var2 Data2 segment var2 dw 0 var3 db 5 dup(?) Data2 ends Code2 segment assume cs:code2,ds:data2 ……… Code2 ends end Extrn lab1:far Public lab2,lab3 Code3 segment assume cs:code3 ……… lab2: ………. lab3: Code3 ends end
四、子程序参数的传递 结构伪操作STRUC struct_name STRUC (DB、DW、DD等伪操作) struct_name ENDS STRUC伪操作只能定义一种结构模式,它并不能把有关信息存入存储器,为了达到这一目的,必须使用结构预置语句,结构顶置语句的格式是: PERSONAL_DATA STRUC INITIALS DB 'XX‘ LAST_NAME DB 5 DUP(?) ID DB 0, 0 AGE DB ? WEIGHT DW ? PERSONAL_DATA ENDS 例: EMPLOYEE_1 PERSONAL_DATA <‘JR’, , ,35> EMPLOYEE_2 PERSONAL_DATA < > EMPLOYEES PERSONAL_DATA 100 DUP (< >) Variable structure name (preassignment specifications)
四、子程序参数的传递 MOV AL, EMPLOYEE_1.LAST_NAME[SI] MOV AL, [BX].LAST_NAME[SI] MOV AL, EMPLOYEES+4*12.LAST_NAME[SI]
§6.2 嵌套与递归子程序 嵌套:子程序调用其他子程序 递归:子程序调用自己,该情况要合理设置出口参数,否则会造成程序死锁
§6.2 嵌套与递归子程序 例:编制计算N! (N>=0)的程序。 N!= N* (N-1) * (N-2) * … * 1
§6.2 嵌套与递归子程序 data_seg segment n_v dw ? result dw ? code1 segment data_seg ends stack_seg segment dw 128 dup(0) tos label word stack_seg ends code1 segment main proc far assume cs:code1,ds:data_seg,ss:stack_seg start: mov ax,stack_seg mov ss,ax mov sp,offset tos push ds sub ax,ax push ax mov ax,data_seg mov ds,ax
§6.2 嵌套与递归子程序 mov bx,offset result push bx mov bx,n_v push bx call far ptr fact ret main endp code1 ends code segment frame struc save_bp dw ? save_cs_ip dw 2 dup(?) n dw ? result_addr dw ? frame ends
assume cs:code fact proc far push bp mov bp,sp push bx push ax mov bx,[bp].result_addr mov ax,[bp].n cmp ax,0 je done push bx dec ax push ax call far ptr fact mov bx,[bp].result_addr mov ax,[bx] mul [bp].n jmp short return done: mov ax,1 return: mov [bx], ax pop ax pop bx pop bp ret 4 fact endp code ends end start
§6.2 嵌套与递归子程序
6.3子程序举例 例6.11 P231 例6.12 P237 下课自学
§6.4 DOS系统功能调用 裸机 ROM-BIOS DOS功能调用 汇编语言程序
系统功能调用 21H号中断是DOS提供给用户的用于调用系统功能的中断,它有近百个功能供用户选择使用,主要包括设备管理、目录管理和文件管理三个方面的功能 ROM-BIOS也以中断服务程序的形式,向程序员提供系统的基本输入输出程序 汇编语言程序设计需要采用系统的各种功能程序 充分利用操作系统提供的资源是程序设计的一个重要方面,需要掌握
功能调用的格式 通常按照如下4个步骤进行: 在AH寄存器中设置系统功能调用号 在指定寄存器中设置入口参数 执行指令INT 21H实现中断服务程序的功能调用 根据出口参数分析功能调用执行情况
字符输出的功能调用 DOS功能调用INT 21H 功能号:AH=02H 入口参数:DL=字符的ASCII码 功能:在显示器当前光标位置显示给定的字符,光标右移一个字符位置。如按Ctrl-Break或Ctrl-C则退出 ;在当前显示器光标位置显示一个问号 mov ah,02h ;设置功能号:ah←02h mov dl,’?' ;提供入口参数:dl←'?' int 21h ;DOS功能调用:显示
字符串输出的功能调用 DOS功能调用INT 21H 可以输出回车(0DH)和换行(0AH)字符产生回车和换行的作用 功能号:AH=09H 入口参数: DS:DX=欲显示字符串在主存中的首地址 字符串应以$(24H)结束 功能:在显示器输出指定的字符串 可以输出回车(0DH)和换行(0AH)字符产生回车和换行的作用
字符串输出的功能调用—显示字符串(例) str db 'Hello,Everybody !',0dh,0ah,'$' ... ;在数据段定义要显示的字符串 ... mov ah,09h ;设置功能号:ah←09h mov dx,offset str ;提供入口参数:dx←字符串的偏移地址 int 21h ;DOS功能调用:显示
字符输入的功能调用 DOS功能调用INT 21H 调用此功能时,若无键按下,则会一直等待,直到按键后才读取该键值 功能号:AH=01H 出口参数:AL=字符的ASCII码 功能:获得按键的ASCII代码值 调用此功能时,若无键按下,则会一直等待,直到按键后才读取该键值
字符输入的功能调用 —判断按键(例) getkey: mov ah,01h ;功能号:ah←01h int 21h ;功能调用 字符输入的功能调用 —判断按键(例) getkey: mov ah,01h ;功能号:ah←01h int 21h ;功能调用 cmp al,’Y’ ;处理出口参数al je yeskey ;是“Y” cmp al,’N’ je nokey ;是“N” jne getkey ... yeskey: ... nokey: ...
字符串输入的功能调用 DOS功能调用INT 21H 执行该功能调用时,用户按键,最后用回车确认 关键要定义好缓冲区 DOS功能调用INT 21H 功能号:AH=0AH 入口参数:DS:DX=缓冲区首地址 执行该功能调用时,用户按键,最后用回车确认 本调用可执行全部标准键盘编辑命令;用户按回车键结束输入,如按Ctrl+Break或Ctrl+C则中止
字符串输入的功能调用 —缓冲区的定义 第1字节事先填入最多欲接收的字符个数(包括回车字符,可以是1~255) 字符串输入的功能调用 —缓冲区的定义 第1字节事先填入最多欲接收的字符个数(包括回车字符,可以是1~255) 第2字节将存放实际输入的字符个数(不包括回车符) 第3字节开始将存放输入的字符串 实际输入的字符数多于定义数时,多出的字符丢掉,且响铃 接收的字符串最后一个总是回车符
字符串输入的功能调用 —输入字符串(例) db 81 dup(0) ;存放输入的字符串 mov ds,dx ;设置数据段DS 字符串输入的功能调用 —输入字符串(例) ;定义缓冲区 Buf db 81 ;第1个字节填入可能输入的最大字符数 db 0 ;存放实际输入的字符数 db 81 dup(0) ;存放输入的字符串 ... mov dx,seg buf ;伪指令seg取得buffer的段地址 mov ds,dx ;设置数据段DS mov dx,offset buffer mov ah,0ah int 21h
补充:程序如何返回DOS 第一种 push ds sub ax,ax push ax … ret
补充:程序如何返回DOS DOS执行文件通常有代码段,数据段,堆栈段,有些还有附加段,当DOS可执行被装入到内存的时候,DOS会在可执行程序的前面加上一个256字节的数据结构,这个结构成为PSP(Program Segment Prefix, 程序段前缀控制块。该块的结构如下:
00H 1 WORD 指令 INT 20H 返回到DOS 02H 1 WORD 程序分配块的底部 04H 1 BYTE 保留 05H 5 BYTE CALL功能调用入口 0AH 2 WORD INT 22H结束地址 0EH 2 WORD INT 23H CTRL-BREAK处理程序地址 12H 2 WORD INT 24H 标准错误处理程序地址 16H 1 WORD 父进程PSP 18H 20 BYTE 句柄表 2CH 1 WORD 环境块地址 2EH 2 WORD 保留 32H 1 WORD 句柄表大小 34H 2 WORD 句柄表地址 38H 24 BYTE 保留 50H 1 WORD INT 21H DOS 调用 52H 1 BYTE FAR RET 53H 9 BYTE 保留 5CH 16 BYTE 为未打开的FCB1使用 6CH 20 BYTE 为未打开的FCB2使用 80H 1 BYTE 命令行参数的长度 81H 127 BYTE 命令行参数 程序真正的代码就紧接在该PSP之后,既从100H开始。 程序调入后,CS和SS会自动指到代码段和堆栈段。 但DS和ES并不指向数据段和附加段,而是指向PSP段地址 RET所执行的操作: IP<-SS:[SP] SP<-SP+2 CS<-SS:[SP]
补充:程序如何返回DOS 第二种 mov ah,4ch int 21h 结束当前执行的程序,并返回父进程DOS或DEBUG(加载并启动它运行的程序)。返回时,AL中保留返回的退出码。
补充:程序如何返回DOS 第三种 mov ah,00h int 21h 该调用要求CS为PSP,实现INT 20h相同的功能
cseg segment org 100h assume cs:cseg ,ds:cseg,es:cseg,ss:cseg start: . . ;程序体 . mov ah,00h int 21h cseg ends end start