32位微机原理与接口技术钱晓捷PPT课件.ppt
第 4 章汇编语言程序设计,4.1 分支程序结构4.2 循环程序结构4.3 子程序结构4.4 Windows应用程序编程4.5 与C+语言的混合编程,4.1 分支程序结构,改变程序执行顺序、形成分支、循环、调用等程序结构是很常见的程序设计问题高级语言采用IF等语句表达条件,并根据条件是否成立转向不同的程序分支汇编语言需要首先利用比较CMP、测试TEST、加减运算、逻辑运算等影响状态标志的指令形成条件然后利用条件转移指令判断由标志表达的条件,并根据标志状态控制程序转移到不同的程序段,4.1.1 无条件转移指令,程序代码在代码段CS:指明代码段在主存中的段基地址EIP:给出将要执行指令的偏移地址程序顺序执行,处理器自动增量EIP程序控制转移,EIP随之改变程序转移到另外的代码段,EIP和CS都改变控制转移类指令:改变EIP(有些也改变CS),即改变程序执行顺序(实现程序控制转移)的指令,本章学习控制转移类指令,1. 转移范围,段内转移在当前代码段范围内的程序转移不需更改CS,只要改变EIP(偏移地址)近转移(Near):32位近转移NEAR32,16位近转移NEAR16短转移(Short):转移范围在127-128字节段间转移从当前代码段跳转到另一个代码段需要更改CS(段地址)和EIP(偏移地址)远转移(Far): 48位远转移FAR32,32位远转移FAR16,2. 指令寻址方式,相对寻址方式提供目标地址相对于当前指令指针EIP的位移量目标地址(转移后的EIP)当前EIP位移量相对寻址都是段内转移,最常用、最灵活直接寻址方式直接提供目标地址目标地址(转移后的CS和EIP)指令操作数间接寻址方式指示寄存器或存储单元目标地址来自寄存器或存储单元、间接获得寄存器间接寻址:用寄存器保存目标地址存储器间接寻址:用存储单元保存目标地址,目标地址目的地址转移地址,3. JMP指令,无条件转移:程序无条件改变执行顺序JMP指令相当于高级语言的goto语句JMP label;程序转向label标号指定的地址;段内相对寻址,段间直接寻址JMP reg32/reg16;程序转向寄存器指定的地址;寄存器间接寻址JMP mem48/mem32/mem16;程序转向存储单元指定的地址;存储器间接寻址,JMP指令的4种类型,1. 段内转移、相对寻址标号指明目标地址,指令代码包含位移量2. 段内转移、间接寻址通用寄存器或主存单元包含目标指令偏移地址3. 段间转移、直接寻址标号包含目标指令的段地址和偏移地址4. 段间转移、间接寻址32位段用3字存储单元包含目标地址16位段用双字存储单元包含目标地址,MASM会根据存储模式等信息自动识别,例4-1无条件转移程序1,; 数据段0000000000000000nvardword ? ; 代码段00000000EB 01jmp labl1; 相对寻址0000000290nop00000003E9 00000001labl1:jmp near ptr labl2; 相对近转移0000000890nop00000009B8 00000011 Rlabl2:mov eax,offset labl3,例4-1无条件转移程序2,0000000EFF E0jmp eax; 寄存器间接寻址0000001090nop00000011B8 00000022 Rlabl3:mov eax,offset labl400000016A3 00000000 Rmov nvar,eax0000001BFF 25 00000000 Rjmp nvar; 存储器间接寻址0000002190noplabl4:,4.1.2 条件转移指令,根据指定的条件确定程序是否发生转移Jcc label;条件满足,发生转移;否则,顺序执行下条指令LABEL表示目标地址,采用段内相对寻址32位IA-32处理器:达到32位的全偏移量16位80 x86处理器:-128+127间的短转移条件转移指令不影响标志,但要利用标志cc表示利用标志判断的条件,16种、两类单个标志状态作为条件两数大小关系作为条件,转移条件cc:单个标志状态,JZ/JEZF=1Jump if Zero/EqualJNZ/JNEZF=0Jump if Not Zero/Not EqualJSSF=1Jump if SignJNSSF=0Jump if Not SignJP/JPEPF=1Jump if Parity/Parity EvenJNP/JPOPF=0Jump if Not Parity/Parity OddJOOF=1Jump if OverflowJNOOF=0Jump if Not OverflowJCCF=1Jump if CarryJNCCF=0Jump if Not Carry,多个助记符方便记忆,转移条件cc:两数大小关系,JB/JNAECF=1Jump if Below/Not Above or EqualJNB/JAECF=0Jump if Not Below/Above or EqualJBE/JNACF=1或ZF=1Jump if Below/Not AboveJNBE/JACF=0且ZF=0Jump if Not Below or Equal/AboveJL/JNGESFOFJump if Less/Not Greater or EqualJNL/JGESF=OFJump if Not Less/Greater or EqualJLE/JNGSFOF或ZF=1Jump if Less or Equal/Not GreaterJNLE/JGSF=OF且ZF=0Jump if Not Less or Equal/Greater,1. 单个标志状态作为条件的条件转移指令,JZ(JE)和JNZ(JNE):利用零位标志ZF判断结果是零(相等)还是非零(不等)JS和JNS:利用符号标志SF判断结果是负还是正JO和JNO:利用溢出标志OF判断结果是溢出还是没有溢出JP(JPE)和JNP(JPO):利用奇偶标志PF判断结果低字节“1”的个数是偶数还是奇数JC和JNC:利用进位标志CF判断结果是有进位(为1)还是无进位(为0),例4-2个数折半程序1,mov eax,885; 假设一个数据shr eax,1; 数据右移进行折半jnc goeven; 余数为0,即CF0条件成立,转移add eax,1; 否则余数为1,即CF1,进行加1操作goeven:call dispuid; 显示结果,443,运行结果,例4-2个数折半程序2,mov eax,886; 假设一个数据shr eax,1; 数据右移进行折半jc goodd; 余数为1,即CF1条件成立,转移到分支体,进行加1操作jmp goeven; 余数为0,即CF0,不需要处理,转移到显示!goodd:add eax,1; 进行加1操作goeven:call dispuid; 显示结果,例4-2个数折半程序3,mov eax,887; 假设一个数据shr eax,1; 数据右移进行折半adc eax,0; 余数CF1,进行加1操作;余数CF0,没有加1call dispuid; 显示结果,mov eax,888; 假设一个数据add eax,1; 个数加1rcr eax,1; 数据右移进行折半call dispuid; 显示结果,改进算法,消除分支,例4-3位测试程序1,; 数据段no_msgbyte Not Ready!,0yes_msgbyte Ready to Go!,0; 代码段mov eax,56h; 假设一个数据test eax,02h; 测试D1位(D11,其他位为0)jz nom; D10条件成立,转移mov eax,offset yes_msg; D11,显示准备好jmp done; 跳转过另一个分支体!nom:mov eax,offset no_msg; 显示没有准备好done:call dispmsg,例4-3位测试程序2,; 数据段no_msgbyte Not Ready!,0yes_msgbyte Ready to Go!,0; 代码段mov eax,56h; 假设一个数据test eax,02h; 测试D1位(D11,其他位为0)jnz yesm; D11条件成立,转移mov eax,offset no_msg; D10,显示没有准备好jmp done; 跳转过另一个分支体!yesm:mov eax,offset yes_msg; 显示准备好done:call dispmsg,例4-4奇校验程序,call readc; 键盘输入, AL返回值call dispcrlf; 回车换行(用于分隔)call dispbb; 以二进制形式显示数据call dispcrlf; 回车换行(用于分隔)and al,7fh; 最高位置“0”、其他位不变; 同时标志PF反映“1”的个数jnp next; 个数为奇数,不需处理,转移or al,80h; 个数为偶数,最高位置“1”、其他位不变 next:call dispbb; 显示含校验位的数据,2. 两数大小关系作为条件的条件转移指令,无符号数用高(Above)、低(Below)低于(不高于等于):JB(JNAE)不低于(高于等于):JNB(JAE)低于等于(不高于):JBE(JNA)不低于等于(高于):JNBE(JA)有符号数用大(Greater)、小(Less)小于(不大于等于):JL(JNGE)不小于(大于等于):JNL(JGE)小于等于(不大于):JLE(JNG)不小于等于(大于):JNLE(JG),例4-5数据比较程序1,; 数据段in_msg1byte Enter a number: ,0in_msg2byte Enter another number: ,0out_msg1byte Two numbers are equal: ,0out_msg2byte The less number is: ,0out_msg3byte 13,10,The greater number is: ,0; 代码段mov eax,offset in_msg1; 提示输入call dispmsgcall readsid; 输入第一个数据mov ebx,eax; 保存到EBXmov eax,offset in_msg2; 提示输入call dispmsgcall readsid; 输入第二个数据mov ecx,eax; 保存到ECX,例4-5数据比较程序2,cmp ebx,ecx; 二个数据进行比较jne nequal; 两数不相等,转移mov eax,offset out_msg1call dispmsg; 显示两数相等mov eax,ebxcall dispsid; 显示相等的数据jmp done; 转移到结束nequal:jl first; EBX较小,不需要交换,转移xchg ebx,ecx; EBX保存较小数,ECX保存较大数,例4-5数据比较程序3,first:mov eax,offset out_msg2; 显示较小数call dispmsgmov eax,ebx; 较小数在EBX中call dispsidmov eax,offset out_msg3; 显示较大数call dispmsgmov eax,ecx; 较大数在ECX中call dispsid done:,4.1.3 单分支结构,只有一个分支的程序类似高级语言的IF-THEN语句结构注意采用正确的条件转移指令当条件满足(成立),发生转移,跳过分支体条件不满足,顺序向下执行分支体条件转移指令与高级语言的IF语句正好相反IF语句是条件成立,执行分支体,例4-6求绝对值程序,; 代码段call readsid; 输入一个有符号数,从EAX返回值cmp eax,0; 比较EAX与0jge nonneg; 条件满足:EAX0,转移neg eax; 条件不满足:EAX0,为负数; 需求补得正值nonneg:call dispuid;分支结束,显示结果,示意图,单分支结构的流程图,返回,neg eax,例4-7字母判断程序,call readc; 输入一个字符,从AL返回值cmp al,A; 与大写字母A比较jb done; 比大写字母A小,不是大写字母,转移cmp al,Z; 与大写字母Z比较ja done; 比大写字母Z大,不是大写字母,转移or al,20h; 转换为小写call dispcrlf; 回车换行call dispc; 显示小写字母done:,4.1.4 双分支结构,双分支程序结构有两个分支,条件为真执行一个分支;条件为假,执行另一个分支相当于高级语言的IF-THEN-ELSE语句顺序执行的分支体1最后一定要有一条JMP指令跳过分支体2JMP指令必不可少,实现结束前一个分支回到共同的出口作用双分支结构有时可以改变为单分支结构事先执行其中一个分支(选择出现概率较高的分支),例4-8显示数据最高位程序1,; 数据段dvardword 0bd630422h; 假设一个数据; 代码段mov ebx,dvarshl ebx,1; EBX最高位移入CF标志jc one; CF1,即最高位为1,转移mov al,0; CF0,即最高位为0:AL0jmp two; 一定要跳过另一个分支one:mov al,1; AL1two:call dispc; 显示,示意图,双分支结构,双分支结构的流程图,返回,例4-8显示数据最高位程序2,mov ebx,dvarmov al,0; 假设最高位为0:AL0shl ebx,1; EBX最高位移入CF标志jnc two; CF0,即最高位为0,转移mov al,1; CF1,即最高位为1,AL1two:call dispc; 显示,单分支结构,例4-9有符号数运算溢出程序,; 数据段dvar1dword 1234567890; 假设两个数据dvar2dword -999999999dvar3dword ?okmsgbyte Correct!,0; 正确信息errmsgbyte ERROR ! Overflow!,0; 错误信息; 代码段mov eax,dvar1 sub eax,dvar2; 求差jo error; 有溢出,转移mov dvar3,eax; 无溢出,保存差值mov eax,offset okmsg; 显示正确jmp disperror:mov eax,offset errmsg; 显示错误disp:call dispmsg,4.2 循环程序结构,三个部分组成:循环初始为开始循环准备必要的条件,如循环次数、循环体需要的初始值等;循环体重复执行的程序代码,其中包括对循环条件的修改等;循环控制判断循环条件是否成立,决定是否继续循环“先判断、后循环”的循环程序结构对应高级语言的WHILE语句“先循环、后判断”的循环程序结构对应高级语言的DO语句,示意图,循环程序结构的流程图,返回,4.2.1 循环指令,LOOP label;ECXECX1;若ECX0,循环到LABEL;否则,顺序执行JECXZ label;ECX0,转移;否则顺序执行目标地址采用相对短转移实地址存储模型使用CX作为计数器,例4-10数组求和程序,mov ecx,lengthof array; ECX数组元素个数xor eax,eax; 求和初值为0mov ebx,eax; 数组指针为0again:add eax,arrayebx*(type array); 求和 inc ebx; 指向下一个数组元素loop againmov sum,eax; 保存结果call dispsid; 显示结果,4.2.2 计数控制循环,通过次数控制循环利用LOOP指令属于计数控制常见是“先循环、后判断”循环结构计数可以减量进行,即减到0结束计数可以增量进行,即达到规定值结束,循环程序结构的关键是如何控制循环,例4-11简单加密解密程序1,; 数据段keybyte 234bufnum= 255bufferbyte bufnum+1 dup(0); 定义键盘输入需要的缓冲区msg1byte Enter messge: ,0msg2byte Encrypted message: ,0msg3byte 13,10,Original messge: ,0; 代码段mov eax,offset msg1; 提示输入字符串call dispmsgmov eax,offset buffer; 设置入口参数EAXcall readmsg; 调用输入字符串子程序push eax; 字符个数保存进入堆栈,例4-11简单加密解密程序2,mov ecx,eax; ECX实际输入的字符个数,作为循环的次数xor ebx,ebx; EBX指向输入字符mov al,key; AL加密关键字encrypt:xor bufferebx,al; 异或加密inc ebxdec ecx; 等同于指令:loop encryptjnz encrypt; 处理下一个字符mov eax,offset msg2call dispmsgmov eax,offset buffer; 显示密文call dispmsg,例4-11简单加密解密程序3,op ecx; 从堆栈弹出字符个数,作为循环的次数xor ebx,ebx; EBX指向输入字符mov al,key; AL解密关键字decrypt:xor bufferebx,al; 异或解密inc ebxdec ecxjnz decrypt; 处理下一个字符mov eax,offset msg3call dispmsgmov eax,offset buffer; 显示明文call dispmsg,示意图,简单加密解密程序运行实例,返回,4.2.3 条件控制循环,根据条件决定是否进行循环需要使用有条件转移指令实现多见“先判断、后循环”结构先行判断的条件控制循环程序很像双分支结构主要分支需要重复执行多次(JMP的目标位置是循环开始)另一个分支用于跳出这个循环先行循环的条件控制循环程序类似单分支结构,循环体就是分支体顺序执行就跳出循环,例4-12字符个数统计程序,; 数据段stringbyte Do you have fun with Assembly?,0; 以0结尾的字符串; 代码段xor ebx,ebx; EBX用于记录字符个数,也用于指向字符的指针again:mov al,stringebxcmp al,0; 用指令“test al,al”更好jz doneinc ebx; 个数加1jmp again; 继续循环done:mov eax,ebx; 显示个数call dispuid,例4-13字符剔除程序1,mov eax,offset string; 显示处理前字符串call dispmsgmov esi,offset stringoutlp:cmp byte ptr esi,0; 外循环,先判断后循环jz done; 为0结束again:cmp byte ptr esi, ; 是否是空格jnz next; 不是空格继续循环mov edi,esi; 是空格,剔除空格分支inlp:inc edi; 该分支是循环程序mov al,edi; 前移一个位置mov edi-1,al,例4-13字符剔除程序2,cmp byte ptr edi,0; 内循环,先循环后判断jnz inlp; 内循环结束处jmp again; 再次判断是否为空格(处理连续空格)next:inc esi; 继续对后续字符进行判断处理jmp outlp; 外循环结束处done:mov eax,offset string; 显示处理后字符串call dispmsg,4.3 子程序结构,经常用到的应用问题编写成一个通用子程序大型处理过程分解成能够解决的模块使用子程序可以使程序的结构更为清楚程序的维护更为方便有利于大程序开发时的多个程序员分工合作,子程序(Subroutine)函数(Function)过程(Procedure),4.3.1 子程序指令,子程序:与主程序分开的、完成特定功能的一段程序当主程序(调用程序)执行调用指令CALL调用子程序子程序(被调用程序)执行返回指令RET返回主程序,回到CALL指令后的指令处,1. 子程序调用指令CALL,CALL指令用在主程序中,实现子程序的调用分成段内调用(近调用)和段间调用(远调用)目标地址采用相对寻址、直接寻址或间接寻址入栈返回地址:将CALL下条指令的地址压入堆栈CALL label;调用标号指定的子程序CALL reg16/reg32;调用寄存器指定地址的子程序CALL mem16/mem32/mem48;调用存储单元指定地址的子程序,2. 子程序返回指令RET,RET指令用在子程序结束,实现返回主程序RET;无参数返回:出栈返回地址RET i16;有参数返回:出栈返回地址;ESPESPi16,MASM会根据存储模型等信息确定子程序的远近调用,并相应产生返回指令,3. 过程定义伪指令,MASM利用过程定义伪指令获得子程序信息 过程名PROC;过程体过程名ENDP;过程名为符合语法的标识符PROC后面可加参数:NEAR或FAR简化段定义源程序格式中,通常不需指定,例4-14子程序调用程序1,; 代码段,主程序00000000B8 00000001mov eax,100000005BD 00000005mov ebp,50000000AE8 00000016call subp; 子程序调用0000000FB9 00000003retp1:mov ecx,300000014BA 00000004retp2:mov edx,400000019E8 00000000 Ecall disprd,例4-14子程序调用程序2,; 子程序subpproc; 过程定义,过程名为subppush ebpmov ebp,espmov esi,ebp+4; ESICALL下条指令(标号RETP1)偏移地址mov edi,offset retp2; EDI标号RETP2的偏移地址mov ebx,2pop ebp; 弹出堆栈,保持堆栈平衡ret; 子程序返回subpendp; 过程结束,示意图,子程序调用的堆栈,返回,4.3.2 子程序设计,子程序的编写方法与主程序一样但需要留意几个问题:利用过程定义,获得子程序名和调用属性RET指令返回主程序,CALL指令调用子程序压入和弹出操作要成对使用,保持堆栈平衡开始保护寄存器,返回前相应恢复安排在代码段的主程序之外子程序允许嵌套和递归,最好有完整的注释,难点是参数传递,回车换行子程序DPCRLF,dpcrlfproc;回车换行子程序push eax;保护寄存器mov al,0dh;输出回车字符call dispc;子程序中调用子程序,实现子程序嵌套mov al,0ah;输出换行字符call dispc;子程序中调用子程序,实现子程序嵌套pop eax;恢复寄存器ret;子程序返回dpcrlfendp,4.3.3 参数传递,主程序与子程序间通过参数传递建立联系入口参数(输入参数):主程序子程序出口参数(输出参数):子程序主程序传递参数的多少反映程序模块间的耦合程度参数的具体内容数据本身(传递数值)数据的存储地址(传递地址,传递引用)参数传递方法寄存器变量堆栈,1. 寄存器传递参数,最简单和常用的参数传递方法把参数存于约定的寄存器少量数据直接传递数值大量数据只能传递地址带有出口参数的寄存器不能保护和恢复带有入口参数的寄存器可以保护、也可以不保护,但最好能够保持一致,例4-15十六进制显示程序1,mov eax, 1234abcdh; 假设一个数据xor ebx,ebxmov ecx,8; 8位十六进制数again:rol eax,4; 高4位循环移位进入低4位push eax; mov edx,eaxcall htoasc; 调用子程序mov regd+4ebx,al; 保存转换后的ASCII码pop eax; mov eax,edxinc ebxdec ecxjnz againmov eax,offset regdcall dispmsg; 显示,regd byte EAX=,8 dup(0),H,0,例4-15十六进制显示程序2,; 子程序htoascproc; 将AL低4位表达的一位十六进制数转换为ASCII码and al,0fh; 只取AL的低4位or al,30h; AL高4位变成3cmp al,39h; 是09,还是AFjbe htoendadd al,7; 是AF,ASCII码再加上7htoend:ret; 子程序返回htoascendp,例4-15十六进制显示程序3,; 子程序htoascproc and eax,0fh; 取AL低4位mov al,ASCIIeax ; 换码ret; 子程序的局部数据(只读)ASCIIbyte 0123456789ABCDEFhtoascendp,EAX=1234ABCDH,运行结果,例4-16有符号十进制数显示程序1,转换的算法如下:(1)首先判断数据是零、正数或负数,是零显示“0”退出(2)是负数,显示负号“”,求数据的绝对值(3)接着数据除以10,余数为十进制数码,加30H转换为ASCII码保存(4)重复(3)步,直到商为0结束(5)依次从高位开始显示各位数字,例4-16有符号十进制数显示程序2,; 数据段arraydword 1234567890,-1234,0,1,.writebufbyte 12 dup(0); 显示缓冲区; 代码段mov ecx,lengthof arraymov ebx,0again:mov eax,arrayebx*4 ; EAX入口参数call write; 调用子程序,显示一个数据call dispcrlf; 换行以便显示下一个数据inc ebxdec ecxjnz again,寄存器传递参数,例4-16有符号十进制数显示程序3,; 显示有符号十进制数的子程序writeproc; EAX入口参数push ebx; 保护寄存器push ecxpush edxmov ebx,offset writebuf ; EBX指向显示缓冲区test eax,eax; 判断数据是零、正数或负数jnz write1; 不是零,跳转mov byte ptr ebx,0; 是零,设置“0”inc ebxjmp write5; 转向显示write1:jns write2; 是正数,跳转mov byte ptr ebx,- ; 是负数,设置负号inc ebxneg eax; 数据求补(绝对值),寄存器传递参数,例4-16有符号十进制数显示程序4,write2:mov ecx,10push ecx; 10压入堆栈,作为退出标志write3:cmp eax,0; 数据(商)为零,转向保存jz write4 xor edx,edx; 零位扩展被除数为EDX.EAXdiv ecx; 数据除以10:EDX.EAX10add edx,30h; 余数(09)转换为ASCII码push edx; 数据先低位后高位压入堆栈jmp write3,例4-16有符号十进制数显示程序5,write4:pop edx; 数据先高位后低位弹出堆栈cmp edx,ecx; 是结束标志10,转向显示je write5mov ebx,dl; 数据保存到缓冲区inc ebxjmp write4write5:mov byte ptr ebx,0; 显示内容加上结尾标志mov eax,offset writebufcall dispmsgpop edx; 恢复寄存器pop ecxpop ebxret; 子程序返回writeendp,2. 共享变量传递参数,子程序和主程序使用同一个变量名存取数据如果变量定义和使用不在同一个程序模块中,需要利用PUBLIC、EXTREN声明共享变量传递参数,子程序的通用性较差特别适合在多个程序段间、尤其在不同的程序模块间传递数据,例4-17二进制数输入程序1,输入一个字符,判断是否合法是字符“0”或“1”合法减去30H转换成数值“0”或“1”重复转换每个字符将前一次的数值左移1位并与新数值进行组合输入非法字符,或超过数据位数提示错误重新输入,例4-17二进制数输入程序2,; 数据段count= 5arraydword count dup(0)tempdword ?; 代码段mov ecx,countmov ebx,offset arrayagain:call rdbd; 调用子程序,输入一个数据mov eax,temp; 获得出口参数mov ebx,eax; 存放到数据缓冲区add ebx,4loop again,共享变量传递参数,例4-17二进制数输入程序3,; 输入二进制数的子程序rdbdproc; 出口参数:变量TEMP补码表示的二进制数值push eax; 说明:负数用“”引导push ebxpush ecxrdbd1:xor ebx,ebx;EBX用于存放二进制结果mov ecx,32;限制输入字符的个数rdbd2:call readc;输入一个字符cmp al,0;检测键入字符是否合法jb rderr;不合法则返回重新输入cmp al,1ja rderr,例4-17二进制数输入程序4,sub al,0;对输入的字符进行转化shl ebx,1;EBX的值乘以2or bl,al;BL和AL相加loop rdbd2;循环键入字符mov temp,ebx;把EBX的二进制结果存放TEMP返回call dispcrlf;分行pop ecxpop ebxpop eaxret;子程序返回,例4-17二进制数输入程序5,rderr:mov eax,offset errmsg;显示错误信息call dispmsgjmp rdbd1errmsgbyte 0dh,0ah,Input error, enter again: ,0rdbdendp,共享变量传递参数,例4-18有符号十进制数输入程序1,十进制有符号整数转换为补码的算法如下:(1)判断输入了正数、还是负数,用一个寄存器记录(2)判断下一个字符是否为有效数码字符无效,提示错误重新输入,并转向(1)字符有效,继续(3)字符有效,减30H转换为二进制数;然后将前面输入的数值乘10,并与刚输入的数字相加得到新的数值(4)判断输入的数据是否超出有效范围超出范围,提示错误重新输入,并转向(1)没有超出范围,则继续(5)重复(2)(4)步,输入字符都有效,一直处理完(6)是负数进行求补,转换成补码;否则直接将数值保存,例4-18有符号十进制数输入程序2,; 数据段count= 10arraydword count dup(0)tempdword ?readbufbyte 30 dup(0); 代码段mov ecx,countmov ebx,offset arrayagain:call read; 调用子程序,输入一个数据mov eax,temp; 获得出口参数mov ebx,eax; 存放到数据缓冲区add ebx,4dec ecxjnz again,共享变量传递参数,例4-18有符号十进制数输入程序3,; 输入有符号十进制数的子程序readproc; 出口参数:变量TEMP补码表示的二进制数值push eax; 说明:负数用“”引导push ebxpush ecxpush edxread0:mov eax,offset readbufcall readmsg; 输入一个字符串test eax,eaxjz readerr; 没有输入数据,错误cmp eax,12ja readerr; 输入超过12个字符,错误,例4-18有符号十进制数输入程序4,mov edx,offset readbuf ; EDX指向输入缓冲区xor ebx,ebx; EBX保存结果xor ecx,ecx;ECX为正负标志,0为正,1为负mov al,edx; 取一个字符cmp al,+; 是“”,继续jz read1cmp al,-; 是“”,设置1标志jnz read2mov ecx,-1read1:inc edx; 取下一个字符mov al,edxtest al,al; 是结尾0,转向求补码jz read3,例4-18有符号十进制数输入程序5,read2:cmp al,0; 不是09之间的数码,错误jb readerrcmp al,9ja readerrsub al,30h; 是09之间的数码,转换imul ebx,10; 原数值乘10:EBXEBX10jc readerr; CF1,乘积溢出,出错movzx eax,al; 零位扩展,便于相加add ebx,eax; 原数乘10后,与新数码相加cmp ebx,80000000h; 数据超过231,出错jbe read1; 继续转换下一个数位,例4-18有符号十进制数输入程序6,readerr:mov eax,offset errmsg; 显示出错信息call dispmsgjmp read0;read3:test ecx,ecx; 判断是正数还是负数jz read4neg ebx; 是负数,进行求补jmp read5read4:cmp ebx,7fffffffh; 正数超过231-1,出错ja readerr,例4-18有符号十进制数输入程序7,read5:mov temp,ebx; 设置出口参数pop edxpop ecxpop ebxpop eaxret; 子程序返回errmsgbyte Input error, enter again: ,0readendp,共享变量传递参数,3. 堆栈传递参数,主程序将入口参数压入堆栈,子程序从堆栈中取出参数出口参数通常不使用堆栈传递高级语言进行函数调用时提供的参数,实质也利用堆栈传递采用堆栈传递参数是程式化的,它是编译程序处理参数传递、以及汇编语言与高级语言混合编程时的常规方法,例4-19计算有符号数平均值程序1,; 数据段arraydword 675,354,-34, .; 代码段push lengthof array; 压入数据个数push offset array; 压数组的偏移地址call mean; 调用求平均值子程序;出口参数:EAX平均值(整数部分)add esp,8; 平衡堆栈(压入了8个字节数据)call dispsid; 显示,堆栈传递参数,例4-19计算有符号数平均值程序2,; 计算32位有符号数平均值子程序meanproc; 入口参数:顺序压入数据个数和数组偏移地址push ebp;出口参数:EAX平均值mov ebp,esppush ebx; 保护寄存器push ecxpush edxmov ebx,ebp+8; EBX取出的偏移地址mov ecx,ebp+12; ECX取出的数据个数xor eax,eax; EAX保存和值xor edx,edx; EDX指向数组元素,堆栈传递参数,例4-19计算有符号数平均值程序3,mean1:add eax,ebx+edx*4; 求和add edx,1; 指向下一个数据cmp edx,ecx; 比较个数jb mean1; 循环cdq; 将累加和EAX符号扩展到EDXidiv ecx; 有符号数除法,EAX平均值pop edx; 恢复寄存器pop ecxpop ebxpop ebpretmeanendp,示意图,利用堆栈传递参数,返回,4.3.4 程序模块,程序分段、子程序等是进行程序模块化开发大型程序时采用的方法子程序模块子程序库库文件包含宏汇编源文件包含,1. 子程序模块,子程序单独编写,汇编形成目标模块OBJ文件连接时输入子程序模块文件名用共用伪指令PUBLIC和外部伪指令EXTERN声明PUBLIC 标识符 ,标识符 ;定义标识符的模块使用EXTERN 标识符:类型 ,标识符:类型 ;调用标识符的模块使用子程序在代码段,与主程序文件采用相同的存储模型,没有开始执行和结束执行点处理好子程序与主程