820 likes | 1.08k Views
第 4 章. 教学重点. 综合应用第 2 章硬指令和第 3 章伪指令,第 4 章从程序结构角度展开程序设计,重点掌握: 分支结构程序设计 循环结构程序设计 子程序结构程序设计. 4.1 顺序程序设计. 顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本、最常见的程序结构. 例 4.1 计算. 例 4.2 移位. 例题 代码转换. .code .startup mov ax,X add ax,Y add ax,Z mov W,ax .exit 0 end. 例 4.1. .model small .stack
E N D
教学重点 综合应用第2章硬指令和第3章伪指令,第4章从程序结构角度展开程序设计,重点掌握: 分支结构程序设计 循环结构程序设计 子程序结构程序设计
4.1 顺序程序设计 • 顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本、最常见的程序结构 例4.1 计算 例4.2 移位 例题 代码转换
.code .startup mov ax,X add ax,Y add ax,Z mov W,ax .exit 0 end 例4.1 .model small .stack .data X dw 5 Y dw 6 Z dw 7 W dw ?
.data qvar dq 1234567887654321h .code mov al,byte ptr qvar[6] mov byte ptr qvar[7],al mov al,byte ptr qvar[5] mov byte ptr qvar[6],al mov al,byte ptr qvar[4] mov byte ptr qvar[5],al mov al,byte ptr qvar[3] mov byte ptr qvar[4],al 例4.2-1/2 图示
例4.2-2/2 mov al,byte ptr qvar[2] mov byte ptr qvar[3],al mov al,byte ptr qvar[1] mov byte ptr qvar[2],al mov al,byte ptr qvar[0] mov byte ptr qvar[1],al mov byte ptr qvar[0],0 图示 1234567887654321h 移位后 3456788765432100h
例题 代码转换-1/2 ;查表法,实现一位16进制数转换为ASCII码显示 .model small .stack 256 .data ASCII db 30h,31h,32h,33h,34h,35h db 36h,37h,38h,39h ;0~9的ASCII码 db 41h,42h,43h,44h,45h,46h ;A~F的ASCII码 hex db 0bh ;任意设定了一个待转换的一位16进制数
.code .startup mov bx,offset ASCII ;BX指向ASCII码表 mov al,hex ;AL取得一位16进制数,正是ASCII码表中位移 and al,0fh ;只有低4位是有效的,高4位清0 xlat;换码:AL←DS:[BX+AL] mov dl,al ;入口参数:DL←AL mov ah,2 ;02号DOS功能调用 int 21h ;显示一个ASCII码字符 .exit 0 end 例题 代码转换-2/2
4.2 分支程序设计 • 分支程序根据条件是真或假决定执行与否 • 判断的条件是各种指令,如CMP、TEST等执行后形成的状态标志 • 转移指令Jcc和JMP可以实现分支控制;还可以采用MASM 6.x提供的条件控制伪指令实现 单分支:求绝对值等 双分支:例4.3等 多分支:例4.4等
第4章 单分支程序设计 • 条件成立跳转,否则顺序执行分支语句体;注意选择正确的条件转移指令和转移目标地址
例题 求绝对值 • Good ;计算AX的绝对值 cmp ax,0 jns nonneg ;分支条件:AX≥0 neg ax ;条件不满足,求补 nonneg:mov result,ax ;条件满足 ;计算AX的绝对值 cmp ax,0 jl yesneg ;分支条件:AX<0 jmp nonneg yesneg:neg ax ;条件不满足,求补 nonneg: mov result,ax ;条件满足 • Bad
例题 无符号数除以2 ;将AX中存放的无符号数除以2,如果是奇数,则加1后除以2 test ax,01h ;测试AX最低位 jz even ;最低位为0:AX为偶数 add ax,1 ;最低位为1:AX为奇数,需要加1 even: rcr ax,1 ;AX←AX÷2 ;如果采用SHR指令,则不能处理AX=FFFFH的特殊情况
第4章 双分支程序设计 条件成立跳转执行第2个分支语句体,否则顺序执行第1个分支语句体。注意第1个分支体后一定要有一个JMP指令跳到第2个分支体后
例题 显示BX最高位 对比 shl bx,1 ;BX最高位移入CF jc one ;CF=1,即最高位为1,转移 mov dl,’0’ ;CF=0,即最高位为0,DL←’0’ jmp two;一定要跳过另一个分支体 one: mov dl,’1’;DL←’1’ two: mov ah,2 int 21h ;显示 • 双分支程序改为单分支程序
例题 显示BX最高位 对比 shl bx,1 ;BX最高位移入CF jnc one ;CF=0,即最高位为0,转移 mov dl,’1’ ;CF=1,即最高位为1,DL←’1’ jmp two;一定要跳过另一个分支体 one: mov dl,’0’;DL←’0’ two: mov ah,2 int 21h ;显示 • 双分支程序改为单分支程序
例题 显示BX最高位 mov dl,’0’;DL←’0’ shl bx,1 ;BX最高位移入CF jnc two ;CF=0,最高位为0,转移 mov dl,’1’;CF=1,最高位为1,DL←’1’ two: mov ah,2 int 21h ;显示 • 编写分支程序,需留心分支的开始和结束
例4.3 判断有无实根-1/2 .startup mov al,_b imul al mov bx,ax ;BX中为b2 mov al,_a imul _c mov cx,4 imul cx;AX中为4ac(DX无有效数据)
例4.3 判断有无实根-2/2 cmp bx,ax ;比较二者大小 jge yes;条件满足? mov tag,0 ;第一分支体:条件不满足,tag←0 jmp done ;跳过第二个分支体 yes: mov tag,1 ;第二分支体:条件满足,tag←1 done: .exit 0
例题 单分支和双分支 ;寄存器AL中是字母Y或y,则令AH=0;否则令AH=-1 cmp al,’Y’;AL是大写Y否? jz next ;是,转移 cmp al,’y’;AL是小写y否? jz next ;是,转移 mov ah,-1 ;不是Y或y,则AH=-1,结束 jmp done;一定要跳过另一个分支体 next: mov ah,0 ;是Y或y,则AH=0,结束 done: ...
第4章 多分支程序设计 图示 • 多个条件对应各自的分支语句体,哪个条件成立就转入相应分支体执行。多分支可以化解为双分支或单分支结构的组合,例如: or ah,ah ;等效于cmp ah,0 jz function0 ;ah=0,转向function0 dec ah ;等效于cmp ah,1 jz function1 ;ah=1,转向function1 dec ah ;等效于cmp ah,2 jz function2 ;ah=2,转向function2
地址表 分支1地址 分支2地址 ... Table db disp1, disp2, disp3, disp4, ... 地址表形成多分支 • 需要在数据段事先安排一个按顺序排列的转移地址表 • 输入的数字作为偏移量。因为只有2个字节16位偏移地址,所以偏移量需要乘2 • 关键是要理解间接寻址方式JMP指令
此处等同于 offset disp1 例4.4 数据段-1/3 .data msg db 'Input number(1~8):',0dh,0ah,'$' msg1 db 'Chapter 1 : ...',0dh,0ah,'$' msg2 db 'Chapter 2 : ...',0dh,0ah,'$‘ ... msg8 db 'Chapter 8 : ... ',0dh,0ah,'$' table dw disp1,disp2,disp3,disp4 dw disp5,disp6,disp7,disp8 ;取得各个标号的偏移地址
例4.4 代码段-2/3 start1: mov dx,offset msg ;提示输入数字 mov ah,9 int 21h mov ah,1 ;等待按键 int 21h cmp al,'1' ;数字 < 1? jb start1 cmp al,'8' ;数字 > 8? ja start1 and ax,000fh ;将ASCII码转换成数字
可以改为 call table[bx] 对应修改为 ret 例4.4 代码段-3/3 dec ax shl ax,1 ;等效于add ax,ax mov bx,ax jmp table[bx] ;(段内)间接转移:IP←[table+bx] start2: mov ah,9 int 21h .exit 0 disp1: mov dx,offset msg1 ;处理程序1 jmp start2 ...
4.3 循环程序设计 • 循环结构一般是根据某一条件判断为真或假来确定是否重复执行循环体 • 循环指令和转移指令可以实现循环控制;还可以采用MASM 6.x提供的循环控制伪指令实现 循环指令LOOP:例4.5等 循环指令LOOPE:例4.6 转移指令:例4.7 多重循环:例4.8等
初始化 循环的初始状态 循环体 循环的工作部分 及修改部分 修改部分 Y 计数控制循环 条件控制循环 控制条件 N 结束 循环结构
例4.5 求和 .model small .stack .data sum dw ? .code .startup xor ax,ax ;被加数AX清0 mov cx,100 again: add ax,cx ;从100,99,...,2,1倒序累加 loop again mov sum,ax ;将累加和送入指定单元 .exit 0 end • 计数控制循环 • 循环次数固定
习题4.16-1/2 ;用二进制显示从键盘输入的一个字符的ASCII码 mov ah,1 ;从键盘输入一个字符 int 21h mov bl,al ;BL←AL=字符的ASCII码 ;DOS功能会改变AL内容,故字符ASCII码存入BL mov ah,2 mov dl,':' ;显示一个分号,用于分隔 int 21h
习题4.16-2/2 mov cx,8 ;CX←8(循环次数) again: shl bl,1 ;左移进CF,从高位开始显示 mov dl,0 ;MOV指令不改变CF adc dl,30h ;DL←0+30H+CF ;CF若是0,则DL←'0';若是1,则DL←'1' mov ah,2 int 21h ;显示 loop again ;CX减1,如果CX未减至0,则循环 • 计数控制循环 • 循环次数固定
例4.6 .startup mov ax,wordX ;测试目标送AX mov cx,16;循环计数器置初值 mov dl,-1 ;计位器置初值 again: inc dl test ax,1 ror ax,1 ;循环指令不影响ZF loope again ;CX≠0且ZF=1(测试位为0),继续循环 je notfound mov byteY,dl jmp done notfound: mov byteY,-1 ;ZF=1,16个位均为0 done: .exit 0 • 计数控制循环 • 最大循环次数固定,满足条件退出
大小写字母仅 D5位不同 例4.7 大小写 mov bx,offset string again: mov al,[bx] ;取一个字符 or al,al ;是否为结尾符0 jz done ;是,退出循环 cmp al,'A' ;是否为大写A~Z jb next cmp al,'Z' ja next or al,20h ;是,转换为小写字母(使D5=1) mov [bx],al ;仍保存在原位置 next: inc bx jmp again ;继续循环 done: .exit 0 • 条件控制循环 • 利用标志退出
冒泡法 图示 • “冒泡法”是一种排序算法,不是最优的算法,但它易于理解和实现 • 冒泡法从第一个元素开始,依次对相邻的两个元素进行比较,使前一个元素不大于后一个元素;将所有元素比较完之后,最大的元素排到了最后;然后,除掉最后一个元素之外的元素依上述方法再进行比较,得到次大的元素排在后面;如此重复,直至完成就实现元素从小到大的排序 • 这需要一个双重循环程序结构
例4.8 movcx,count ;CX←数组元素个数 dec cx ;元素个数减1为外循环次数 outlp: movdx,cx ;DX←内循环次数 mov bx,offset array inlp:mov al,[bx] ;取前一个元素 cmp al,[bx+1] ;与后一个元素比较 jna next ;前一个不大于后一个元素,则不进行交换 xchg al,[bx+1] ;否则,进行交换 mov [bx],al next: inc bx ;下一对元素 dec dx jnz inlp ;内循环尾 loop outlp ;外循环尾 • 计数控制双重循环
例4.9 剔除空格-1/2 ;现有一个以$结尾的字符串,要求剔除其中的空格 .data string db ’Let us have a try !’,’$’ .code .startup mov si,offset string outlp: cmp byte ptr [di],’$’ ;外循环,先判断后循环 jz done ;为$结束 cmp byte ptr [si],’’ ;检测是否是空格 jnz next ;不是空格继续循环
例4.9 剔除空格-2/2 • 条件控制双重循环 mov di,si ;是空格,进入剔除空格分支 ;该分支是循环程序段 inlp: inc di mov al,[di] ;前移一个位置 mov [di-1],al cmp byte ptr [di],’$’ ;内循环,先循环后判断 jnz inlp jmp outlp next: inc si ;继续对后续字符进行处理 jmp outlp done: .exit 0 ;结束
4.4 子程序设计 • 把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序 • 子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率 子程序设计要利用过程定义伪指令 参数传递是子程序设计的重点和难点 子程序可以嵌套; 一定条件下,还可以递归和重入
过程名 proc [near|far] ... 过程名 endp 过程名(子程序名)为符合语法的标识符 NEAR属性(段内近调用)的过程只能被相同代码段的其他程序调用 FAR属性(段间远调用)的过程可以被相同或不同代码段的程序调用 对简化段定义格式,在微型、小型和紧凑存储模式下,过程的缺省属性为near;在中型、大型和巨型存储模式下,过程的缺省属性为far 对完整段定义格式,过程的缺省属性为near 用户可以在过程定义时用near或far改变缺省属性 4.4.1 程序定义伪指令
第4章 子程序的常见格式 subname proc ;具有缺省属性的subname过程 push ax ;保护寄存器:顺序压入堆栈 push bx ;ax/bx/cx仅是示例 push cx … ;过程体 pop cx ;恢复寄存器:逆序弹出堆栈 pop bx pop ax ret ;过程返回 subname endp ;过程结束
例题 无参数传递的子程序 ;子程序功能:实现光标回车换行 dpcrlf proc;过程开始 push ax;保护寄存器AX和DX push dx mov dl,0dh;显示回车 mov ah,2 int 21h mov dl,0ah;显示换行 mov ah,2 int 21h pop dx;恢复寄存器DX和AX pop ax ret;子程序返回 dpcrlf endp;过程结束
例4.10 子程序-1/3 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,还要加上7 aldisp1: mov ah,2 ;显示 int 21h
例4.10 子程序-2/3 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 主程序-3/3 ... ;主程序,同例4.8源程序 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 ... ;过程定义 end
例题 具有多个出口的子程序 HTOASC proc ;将AL低4位表达的一位16进制数转换为ASCII码 and al,0fh cmp al,9 jbe htoasc1 add al,37h ;是0AH~0FH,加37H ret;子程序返回 htoasc1: add al,30h ;是0~9,加30H ret;子程序返回 HTOASC endp
4.4.2 子程序的参数传递 • 入口参数(输入参数):主程序提供给子程序 • 出口参数(输出参数):子程序返回给主程序 • 参数的形式: ① 数据本身(传值) ② 数据的地址(传址) • 传递的方法: ① 寄存器 ② 变量 ③ 堆栈
子程序计算数组元素的“校验和” 校验和是指不记进位的累加 入口参数: 数组的逻辑地址(传址) 元素个数(传值) 出口参数: 求和结果(传值) 例4.11 求校验和
用寄存器传递参数 • 把参数存于约定的寄存器中,可以传值,也可以传址。 • 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护) • 子程序对带有入口参数的寄存器可以保护,也可以不保护;但最好一致 例4.11a 入口参数:CX=元素个数, DS:BX=数组的段地址:偏移地址 出口参数:AL=校验和
例4.11a 主程序 .startup ;设置入口参数(含有DS←数组的段地址) mov bx,offset array ;BX←数组的偏移地址 mov cx,count ;CX←数组的元素个数 call checksuma;调用求和过程 mov result,al ;处理出口参数 .exit 0
例4.11a 子程序 checksuma proc xor al,al ;累加器清0 suma: add al,[bx];求和 inc bx ;指向下一个字节 loop suma ret checksuma endp end
用变量传递参数 • 主程序和子程序直接采用同一个变量名共享同一个变量,实现参数的传递 • 不通模块间共享时,需要声明 例4.11b 入口参数: count=元素个数, array=数组名(含段地址:偏移地址) 出口参数: result=校验和
例4.11b-1/2 ;主程序 call checksumb ;子程序 checksumb proc push ax push bx push cx xor al,al ;累加器清0 mov bx,offset array ;BX←数组的偏移地址 mov cx,count ;CX←数组的元素个数