1 / 91

第 5 章 模块化程序设计

5.1 子程序结构 5.2 参数传递 5.3 多模块程序结构 5.4 宏结构. 第 5 章 模块化程序设计. 问题的引入 子程序、文件包含、宏汇编等各种多模块编程的方 法. 第 5 章 模块化程序设计. #include &quot;stdafx.h&quot; #include &quot;stdio.h&quot; int main(int argc, char* argv[]) { int a,b,c; a=1; b=2; c=a+b; printf(“c=%d<br>&quot;,c);

joshwa
Download Presentation

第 5 章 模块化程序设计

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 5.1 子程序结构 5.2 参数传递 5.3 多模块程序结构 5.4 宏结构 第5章 模块化程序设计

  2. 问题的引入 子程序、文件包含、宏汇编等各种多模块编程的方法 第5章 模块化程序设计

  3. #include "stdafx.h" #include "stdio.h" int main(int argc, char* argv[]) { int a,b,c; a=1; b=2; c=a+b; printf(“c=%d\n",c); return 0; } #include "stdafx.h" #include "stdio.h" int main(int argc, char* argv[]) { int a,b,c; a=1; b=2; c=a+b; printf(“c=%d\n",c); return 0; } 例1: 利用C语言编写计算c=a+b

  4. 例2:用汇编编写: mov a,1 mov b,2 mov al,a add al,b mov c,al lea dx,string mov ah,09 int 21h add c,30h mov dl,c mov ah,2 int 21h mov dl,0ah int 21h mov dl,0dh int 21h ret main endp code ends end start data segment a db ? b db ? c db ? string db 'c=$' data ends code segment main proc far assume cs:code, ds:data, es:data start: push ds sub ax,ax push ax mov ax,data mov ds,ax mov es,ax

  5. 5.1 子程序结构 • 经常用到的应用问题编写成一个通用子程序 • 大型处理过程分解成能够解决的模块 • 使用子程序可以 • 使程序的结构更为清楚 • 程序的维护更为方便 • 有利于大程序开发时的多个程序员分工合作 子程序(Subroutine) =函数(Function) =过程(Procedure)

  6. 5.1.1 子程序指令 主程序 • 子程序 • 与主程序分开、完成特定功能的一段程序 • 主程序(调用程序) • 执行调用指令CALL调用子程序 • 子程序(被调用程序) • 执行返回指令RET返回主程序 CALL label 子程序 RET 回到CALL指令后的指令处-断点

  7. CALL 1. 子程序调用指令CALL • CALL指令用在主程序中,实现子程序的调用 • 分成段内调用(近调用)和段间调用(远调用) • 目标地址采用相对寻址、直接寻址或间接寻址 • 入栈返回地址:将CALL下条指令的地址压入堆栈 CALL label ;调用标号指定的子程序 CALL reg32/reg16 ;调用寄存器指定地址的子程序 CALL mem48/mem32/mem16 ;调用存储单元指定地址的子程序 EIP CS Stack CALL

  8. RET 2. 子程序返回指令RET • RET指令用在子程序结束,实现返回主程序 RET ;无参数返回:出栈返回地址 RET i16 ;有参数返回:出栈返回地址 ;ESP←ESP+i16 EIP CS Stack CALL RET MASM会根据存储模型等信息确定子程序的远近调用,并相应产生返回指令

  9. 3. 过程定义伪指令 • MASM利用过程定义伪指令获得子程序信息 过程名 PROC …… ;过程体 过程名 ENDP ;过程名为符合语法的标识符 • PROC后面可加参数:NEAR或FAR • 简化段定义源程序格式中,通常不需指定

  10. 〔例5-1〕子程序调用程序-1 ;代码段,主程序 00000000 B8 00000001 mov eax,1 00000005 BD 00000005 mov ebp,5 0000000A E8 00000016call subp;子程序调用 0000000F B9 00000003 retp1: mov ecx,3 00000014 BA 00000004 retp2: mov edx,4 00000019 E8 00000000 Ecall disprd ;代码段,子程序 00000025 00000025 subp proc offset retp1 Stack CALL

  11. MOV [EBP+4],EDI 〔例5-1〕子程序调用程序-2 ;代码段,子程序 subp proc ;过程定义,过程名为subp push ebp mov ebp,esp mov esi,[ebp+4] ;ESI=CALL下条指令(标号RETP1)偏移地址 mov edi,offset retp2 ;EDI=标号RETP2的偏移地址 mov ebx,2 pop ebp ;弹出堆栈,保持堆栈平衡 ret ;子程序返回 subp endp ;过程结束 示意图

  12. 5.1.2 子程序设计 • 子程序的编写方法与主程序一样 • 但需要留意几个问题: • 利用过程定义,获得子程序名和调用属性 • RET指令返回主程序,CALL指令调用子程序 • 压入和弹出操作要成对使用,保持堆栈平衡 • 开始保护寄存器,返回前相应恢复 • 安排在代码段的主程序之外 • 子程序允许嵌套和递归 最好有完整的注释 难点是参数传递

  13. 〔例5-2〕回车换行子程序 dpcrlf proc ;回车换行子程序 push eax ;保护寄存器 mov al,0dh ;输出回车字符 call dispc ;子程序中调用子程序,实现子程序嵌套 mov al,0ah ;输出换行字符 call dispc pop eax ;恢复寄存器 ret ;子程序返回 dpcrlf endp

  14. 重点 5.2 参数传递 • 主程序与子程序间通过参数传递建立联系 • 入口参数(输入参数):主程序→子程序 • 出口参数(输出参数):子程序→主程序 • 传递参数的多少反映程序模块间的耦合程度 • 参数的具体内容 • 数据本身(传递数值) • 数据的存储地址(传递地址,传递引用) • 参数传递方法 • 寄存器 • 变量 • 堆栈

  15. 子程序计算数组元素的“校验和” 校验和是指不记进位的累加 入口参数: 数组的偏移地址(传址) 元素个数(传值) 出口参数: 求和结果(传值) 求校验和

  16. 用寄存器传递参数 • 把参数存于约定的寄存器中,可以传值,也可以传址。 • 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护) • 子程序对带有入口参数的寄存器可以保护,也可以不保护;但最好一致 例a 入口参数:ECX=元素个数, EBX=数组的偏移地址 出口参数:AL=校验和

  17. 例a 主程序 ;数据段 array byte 32,78,56,... count equ $-array result byte ? ;代码段,设置入口参数 mov ebx,offset array ;EBX←数组的偏移地址 mov ecx,count;ECX←数组的元素个数 call checksuma ;调用求和过程 mov result,al;处理出口参数

  18. 例a 子程序 checksuma proc xor al,al;累加器清0 suma: add al,[ebx];求和 inc ebx;指向下一个字节 loop suma ret checksuma endp end

  19. 用变量传递参数 • 主程序和子程序直接采用同一个变量名共享同一个变量,实现参数的传递 • 不同模块间共享时,需要声明 例b 入口参数: count=元素个数, array=数组名(偏移地址) 出口参数: result=校验和

  20. 例b-1/2 ;数据段 array byte 32,78,56,... count dword $-array result byte ? ;主程序 call checksumb ;子程序 checksumb proc push eax push ebx push ecx

  21. 例b-2/2 xor al,al;累加器清0 mov ebx,offset array;EBX←偏移地址 mov ecx,count;ECX←元素个数 sumb: add al,[ebx];求和 inc ebx loop sumb mov result,al ;保存校验和 pop ecx pop ebx pop eax ret checksumbendp

  22. 用堆栈传递参数 • 主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数 • 子程序将出口参数压入堆栈,主程序弹出堆栈取得它们 例c 入口参数: 顺序压入偏移地址和元素个数 出口参数: AL=校验和

  23. 例c 主程序 ;数据段 array byte 32,78,56,... count equ $-array result byte ? mov eax,offset array push eax mov eax,count push eax call checksumc add esp,8 mov result,al EIP CALL count PUSH AX offset array PUSH AX Stack

  24. 例c 子程序-1/2 checksumc proc push ebp mov ebp,esp;EBP间接寻址存取参数 push ebx push ecx mov ebx,[ebp+12] ;偏移地址 mov ecx,[ebp+8];元素个数 EBP EIP +4 count +8 offset array +12 Stack PUSH

  25. 例c 子程序-2/2 xor al,al sumc: add al,[ebx] inc ebx loop sumc pop ecx pop ebx pop ebp ret checksumc endp EBP EIP count offset array Stack POP

  26. 5.2.1 寄存器传递参数 • 最简单和常用的参数传递方法 • 把参数存于约定的寄存器 • 少量数据直接传递数值 • 大量数据只能传递地址 • 带有出口参数的寄存器不能保护和恢复 • 带有入口参数的寄存器可以保护、也可以不保护,但最好能够保持一致

  27. 〔例5-3〕十六进制显示程序-1 mov eax, 1234abcdh ;假设一个数据 xor ebx,ebx mov ecx,8 ;8位十六进制数 again: rol eax,4 ;高4位循环移位进入低4位 push eax ;mov edx,eax call htoasc ;调用子程序 mov regd+4[ebx],al ;保存转换后的ASCII码 pop eax ;mov eax,edx inc ebx dec ecx jnz again mov eax,offset regd call dispmsg ;显示 regd byte 'EAX=',8 dup(0),'H',0

  28. 〔例5-3〕十六进制显示程序-2 ;子程序 htoasc proc ;将AL低4位表达的一位十六进制数转换为ASCII码 and al,0fh ;只取AL的低4位 or al,30h ;AL高4位变成3 cmp al,39h ;是0~9,还是A~F jbe htoend add al,7 ;是A~F,ASCII码再加上7 htoend:ret ;子程序返回 htoasc endp

  29. 〔例5-3〕十六进制显示程序-3 ;子程序 htoasc proc and eax,0fh ;取AL低4位 mov al,ASCII[eax] ;换码 ret ;子程序的局部数据(只读) ASCII byte '0123456789ABCDEF' htoasc endp EAX=1234ABCDH 运行结果

  30. 〔例5-4〕有符号十进制数显示程序-1 • 转换的算法如下: 1)首先判断数据是零、正数或负数,是零显示“0”退出 2)是负数,显示负号“-”,求数据的绝对值 3)接着数据除以10,余数为十进制数码,加30H转换为ASCII码保存 4)重复第(3)步,直到商为0结束 5)依次从高位开始显示各位数字

  31. 〔例5-4〕有符号十进制数显示程序-2 ;数据段 array dword 1234567890,-1234,0,1,... writebuf byte 12 dup(0) ;显示缓冲区 ;代码段 mov ecx,lengthof array mov ebx,0 again: mov eax,array[ebx*4] ;EAX=入口参数 call write ;调用子程序,显示一个数据 call dispcrlf ;换行以便显示下一个数据 inc ebx dec ecx jnz again 寄存器传递参数

  32. 〔例5-4〕有符号十进制数显示程序-3 ;显示有符号十进制数的子程序 write proc ;EAX=入口参数 push ebx ;保护寄存器 push ecx push edx mov ebx,offset writebuf ;EBX指向显示缓冲区 test eax,eax ;判断数据是零、正数或负数 jnz write1 ;不是零,跳转 mov byte ptr [ebx],'0' ;是零,设置“0” inc ebx jmp write5 ;转向显示 write1: jns write2 ;是正数,跳转 寄存器传递参数

  33. 〔例5-4〕有符号十进制数显示程序-4 mov byte ptr [ebx],'-' ;是负数,设置负号 inc ebx neg eax ;数据求补(绝对值) write2: mov ecx,10 push ecx ;10压入堆栈,作为退出标志 write3: cmp eax,0 ;数据(商)为零,转向保存 jz write4 xor edx,edx ;零位扩展被除数为EDX.EAX div ecx ;数据除以10:EDX.EAX÷10 add edx,30h ;余数(0~9)转换为ASCII码 push edx ;数据先低位后高位压入堆栈 jmp write3

  34. 〔例5-4〕有符号十进制数显示程序-5 write4: pop edx ;数据先高位后低位弹出堆栈 cmp edx,ecx ;是结束标志10,转向显示 je write5 mov [ebx],dl ;数据保存到缓冲区 inc ebx jmp write4 write5: mov byte ptr [ebx],0 ;显示内容加上结尾标志 mov eax,offset writebuf call dispmsg pop edx ;恢复寄存器 pop ecx pop ebx ret ;子程序返回 write endp

  35. 5.2.2 共享变量传递参数 • 子程序和主程序使用同一个变量名存取数据 • 如果变量定义和使用不在同一个程序模块中,需要利用PUBLIC、EXTREN声明 • 共享变量传递参数,子程序的通用性较差 • 特别适合在多个程序段间、尤其在不同的程序模块间传递数据

  36. 〔例5-5〕二进制输入程序-1 ;数据段 count = 5 array dword count dup(0) temp dword ? ;共享变量 ;代码段,主程序 mov ecx,count mov ebx,offset array again: call rdbd;调用子程序,输入一个数据 mov eax,temp;获得出口参数 mov [ebx],eax;存放到数据缓冲区 add ebx,4 loop again

  37. 〔例5-5〕二进制输入程序-2 ;代码段,子程序 rdbd proc;二进制输入子程序 push eax;出口参数:共享变量TEMP push ebx push ecx rdbd1: xor ebx,ebx;EBX用于存放二进制结果 mov ecx,32;限制输入字符的个数 rdbd2: call readc;输入一个字符 cmp al,'0';检测键入字符是否合法 jb rderr;不合法则返回重新输入 cmp al,'1' ja rderr

  38. 〔例5-5〕二进制输入程序-3 sub al,'0';对输入的字符进行转化 shl ebx,1;EBX的值乘以2 or bl,al;BL和AL相加 loop rdbd2;循环键入字符 mov temp,ebx;把二进制结果存放TEMP返回 call dispcrlf;分行 pop ecx pop ebx pop eax ret

  39. 〔例5-5〕二进制输入程序-4 rderr: mov eax,offset errmsg ;显示错误信息 call dispmsg jmp rdbd1 errmsg byte 0dh,0ah,'Input error, enter again: ',0 rdbd endp

  40. 〔例5-6〕有符号十进制数输入程序-1 • 十进制有符号整数转换为补码的算法如下: 1)判断输入了正数、还是负数,用一个寄存器记录 2)判断下一个字符是否为有效数码 字符无效,提示错误重新输入,并转向(1) 字符有效,继续 3)字符有效,减30H转换为二进制数;然后将前面输入的数值乘10,并与刚输入的数字相加得到新的数值 4)判断输入的数据是否超出有效范围 超出范围,提示错误重新输入,并转向(1) 没有超出范围,则继续 5)重复第(2)~(4)步,输入字符都有效,一直处理完 6)是负数进行求补,转换成补码;否则直接将数值保存

  41. 〔例5-6〕有符号十进制数输入程序-2 ;数据段 count = 10 array dword count dup(0) temp dword ? readbuf byte 30 dup(0) ;代码段 mov ecx,count mov ebx,offset array again: call read ;调用子程序,输入一个数据 mov eax,temp ;获得出口参数 mov [ebx],eax ;存放到数据缓冲区 add ebx,4 dec ecx jnz again 共享变量传递参数

  42. 〔例5-6〕有符号十进制数输入程序-3 ;输入有符号十进制数的子程序 read proc ;出口参数:变量TEMP=补码表示的二进制数值 push eax ;说明:负数用“-”引导 push ebx push ecx push edx read0: mov eax,offset readbuf call readmsg ;输入一个字符串 test eax,eax jz readerr ;没有输入数据,错误 cmp eax,12 ja readerr ;输入超过12个字符,错误

  43. 〔例5-6〕有符号十进制数输入程序-4 mov edx,offset readbuf ;EDX指向输入缓冲区 xor ebx,ebx ;EBX保存结果 xor ecx,ecx ;ECX为正负标志,0为正,-1为负 mov al,[edx] ;取一个字符 cmp al,'+' ;是“+”,继续 jz read1 cmp al,'-' ;是“-”,设置-1标志 jnz read2 mov ecx,-1 read1: inc edx ;取下一个字符 mov al,[edx] test al,al ;是结尾0,转向求补码 jz read3

  44. 〔例5-6〕有符号十进制数输入程序-5 read2: cmp al,'0' ;不是0~9之间的数码,错误 jb readerr cmp al,'9' ja readerr sub al,30h ;是0~9之间的数码,转换 imul ebx,10 ;原数值乘10:EBX=EBX×10 jc readerr ;CF=1,乘积溢出,出错 movzx eax,al ;零位扩展,便于相加 add ebx,eax ;原数乘10后,与新数码相加 cmp ebx,80000000h ;数据超过231,出错 jbe read1 ;继续转换下一个数位

  45. 〔例5-6〕有符号十进制数输入程序-6 readerr: mov eax,offset errmsg ;显示出错信息 call dispmsg jmp read0 ; read3: test ecx,ecx ;判断是正数还是负数 jz read4 neg ebx ;是负数,进行求补 jmp read5 read4: cmp ebx,7fffffffh ;正数超过231-1,出错 ja readerr

  46. 〔例5-6〕有符号十进制数输入程序-7 read5: mov temp,ebx ;设置出口参数 pop edx pop ecx pop ebx pop eax ret ;子程序返回 errmsg byte 'Input error, enter again: ',0 read endp 共享变量传递参数

  47. 5.2.3 堆栈传递参数 • 主程序将入口参数压入堆栈,子程序从堆栈中取出参数 • 出口参数通常不使用堆栈传递 • 高级语言进行函数调用时提供的参数,实质也利用堆栈传递 • 采用堆栈传递参数是程式化的,它是编译程序处理参数传递、以及汇编语言与高级语言混合编程时的常规方法

  48. 〔例5-7〕计算有符号数平均值程序-1 ;数据段 array dword 675,354,-34, ... ;代码段 push lengthof array ;压入数据个数 push offset array ;压数组的偏移地址 call mean ;调用求平均值子程序 ;出口参数:EAX=平均值(整数部分) add esp,8 ;平衡堆栈(压入了8个字节数据) call dispsid ;显示 堆栈传递参数

  49. 〔例5-7〕计算有符号数平均值程序-2 ;计算32位有符号数平均值子程序 mean proc ;入口参数:顺序压入数据个数和数组偏移地址 push ebp ;出口参数:EAX=平均值 mov ebp,esp push ebx ;保护寄存器 push ecx push edx mov ebx,[ebp+8] ;EBX=取出的偏移地址 mov ecx,[ebp+12];ECX=取出的数据个数 xor eax,eax ;EAX保存和值 xor edx,edx ;EDX=指向数组元素 堆栈传递参数

  50. 〔例5-7〕计算有符号数平均值程序-3 mean1: add eax,[ebx+edx*4] ;求和 add edx,1 ;指向下一个数据 cmp edx,ecx ;比较个数 jb mean1 ;循环 cdq ;将累加和EAX符号扩展到EDX idiv ecx ;有符号数除法,EAX=平均值 pop edx ;恢复寄存器 pop ecx pop ebx pop ebp ret mean endp 示意图

More Related