一、顺序程序结构
- 程序的静态分析 略
- 程序的动态分析 略
- 实例:读取CMOS RAM数据程序 略
二、分支程序结构
- 无条件转移指令
- 代码段寄存器CS指出代码段的段基地址,指令指针IP给出将要执行的偏移地址
- 转移范围
- 段内转移——只改变IP
- 段内转移也称近转移(NEAR)
- 如果转移范围可以用1字节编码表达,即向地址增大方向转移127字节,向地址减小方向转移128字节,形成短转移(SHORT)
- 段间转移——更改CS和IP
- 也称远转移(FAR)
- 段内转移——只改变IP
- 指令寻址方式
- 相对寻址方式
- 提供目标地址相对于当前指令指针IP的位移量
- 目标地址(转移后的IP)=当前IP+位移量
- 直接寻址方式
- 直接提供目标地址
- 目标地址(转移后的CS和IP)=指令操作数
- 间接寻址方式
- 指示寄存器或存储单元
- 目标地址来自寄存器或存储单元,间接获得
- 注:目标地址=目的地址=转移地址
- 相对寻址方式
- JMP指令
- 四种类型
- 段内转移,相对寻址
JMP LABLE ;IP=IP+位移量
- 段内转移,间接寻址
JMP R16 ;IP=R16 寄存器间接寻址 JMP M16 ;IP=M16 存储器间接寻址
- 段间转移,直接寻址
JMP LABLE ;IP=LABLE的偏移地址,CS=LABLE的段选择器
- 段间转移,间接寻址
JMP M32 ;IP=M32,CS=M32+2
- 段内转移,相对寻址
- MASM会根据存储模型和目标地址等信息自动识别是哪种转移。
- 汇编程序提供了操作符:
- SHORT
- NEAR PTR
- FAR PTR
- 可以强制转换一个符号、段名或子程序名的类型,形成相应的控制转移。
- 汇编程序提供了操作符:
- 实例:
;eg403.asm .model small .stack .data nvar dw ? fvar dd ? .code .startup labl0: jmp labl1 ;段内(短)转移、相对寻址 nop labl1: jmp near ptr labl2 ;段内(近)转移、相对寻址 nop labl2: mov ax,offset labl3 jmp ax ;段内转移、寄存器间接寻址 nop labl3: mov ax,offset labl4 mov nvar,ax jmp nvar ;段内转移、存储器间接寻址 nop labl4: jmp far ptr labl5 ;段间转移、直接寻址 nop labl5: mov ax,offset labl6 mov word ptr fvar,ax mov dx,seg labl6 mov word ptr fvar+2,dx jmp fvar ;段间转移、间接寻址 nop labl6: .exit end
- 四种类型
- 条件转移指令
- 格式及说明
-
JCC LABEL ;条件满足,发生转移; ;否则顺序执行下条指令
- LABEL表示目标地址,采用段内相对寻址(只能是-128~127之间的短转移)
- 条件转移指令不影响标志,但要利用标志。
- 16种,两类标志判断如下:
- 单个标志状态作为条件
- 两数大小关系作为条件
-
- 实例!开始疯狂上代码!
- 个数折半(如果是奇数就加一,如果是偶数直接显示)
-
mov ax,885 ;假设一个数据 shr ax,1 ;数据右移进行折半 jnc goeven ;余数为0,即CF=0条件成立,不需要处理,转移 add ax,1 ;否则余数为1,即CF=1,进行加1操作 goeven: call dispuiw ;显示结果
- 上述代码可以利用ADC优化成:
mov ax,887 ;假设一个数据 shr ax,1 ;数据右移进行折半 adc ax,0 ;余数=CF=1,进行加1操作;余数=CF=0,没有加1 call dispuiw ;显示结果
-
-
位测试程序(如果AL的D1=1 显示“READY TO" GO! D1=0显示”NOT READY")
-
.data no_msg db 'Not Ready!','$' yes_msg db 'Ready to Go!','$' .code .startup mov al,56h ;假设一个数据 test al,02h ;测试D1位(使用D1=1,其他位为0的数据) jz nom ;D1=0条件成立,转移 mov dx,offset yes_msg ;D1=1,显示准备好 jmp done ;跳转过另一个分支体! nom: mov dx,offset no_msg ;显示没有准备好 done: mov ah,9 int 21h
-
- 奇偶校验程序(在字符的ASC码中的1的个数为奇数令校验位为0)程序实现在ASC码的最高位加上校验位
-
Tdata db ? ;保存待发送数据的变量 mov ah,1 ;1号功能 int 21h ;键盘输入 and al,7fh ;最高位置“0”、其他位不变,同时标志PF反映“1”的个数 jnp next ;个数为奇数,则转向NEXT or al,80h ;最高位置“1”、其他位不变 next: mov Tdata,al ;保存待发送的数据
-
-
数据大小比较程序(相等显示“EQUAL”,第一个数大显示“FIRST”,第二个数大显示“SECOND”)
-
;eg407.asm .model small .stack .data var1 dw -3765 var2 dw 8930 msg0 db 'Equal$' msg1 db 'First$' msg2 db 'Second$' .code .startup mov ax,var1 ;取第1个数据 cmp ax,var2 ;与第2个数据比较 je equal ;两数相等,转移 jnl first ;第1个数据大,转移 mov dx,offset msg2 ;第2个数据大 jmp done first: mov dx,offset msg1 jmp done equal: mov dx,offset msg0 done: mov ah,9 ;显示结果 int 21h .exit end
-
- 个数折半(如果是奇数就加一,如果是偶数直接显示)
- 格式及说明
- 单分支
-
- 求绝对值
-
;eg408.asm .model small .stack .data var dw 0b422h ;有符号数据 result dw ? ;保存绝对值 .code .startup mov ax,var cmp ax,0 ;比较AX与0 jge nonneg ;条件满足:AX≥0,转移 neg ax ;条件不满足:AX<0,为负数,需求补得正值 nonneg: mov result,ax ;分支结束,保存结果 .exit end
-
-
字母判断程序
-
mov ah,1 int 21h ;输入一个字符,从AL返回值 cmp al,'A' ;与大写字母A比较 jb done ;比大写字母A小,不是大写字母,转移 cmp al,'Z' ;与大写字母Z比较 ja done ;比大写字母Z大,不是大写字母,转移 or al,20h ;转换为小写 mov dl,al mov ah,2 int 21h ;显示小写字母 done:
-
- 求绝对值
-
- 双分支程序
-
- 显示数据最高位
-
var dw 0b422h ;有符号数据 mov bx,var 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 ;显示
可以使用ADC消除分支:
-
XOR DL,DL mov bx,var shl bx,1 ;BX最高位移入CF标志 adc dl,'0' mov ah,2 int 21h ;显示
-
- 有符号数运算溢出程序
-
;eg411.asm .model small .stack .data var1 dw 24680 var2 dw -9999 var3 dw ? okmsg db 'Correct!','$' ;正确信息 errmsg db 'ERROR ! Overflow!','$' ;错误信息 .code .startup mov ax,var1 sub ax,var2 ;求差 jo error ;有溢出,转移 mov var3,ax ;无溢出,保存差值 mov dx,offset okmsg ;显示正确 jmp disp error: mov dx,offset errmsg ;显示错误 disp: mov ah,9 int 21h .exit end
-
- 显示数据最高位
-
- 多分支程序
- 可以使用表结构实现多分支
三、循环程序结构
- 循环指令
- LOOP LABEL
- CX=CX-1 如果CX不等于0,循环到label,否则顺序执行
- JCXZ LABEL
- CX=0转移,否则顺序执行
- 目标地址采用相对短转移
- 使用CX作为计数器
- 如果CX=0将循环pow(2,16)次发生错误
- 此时可以使用JCXZ先判断CX是否为零
- 实例:数组求和程序
-
ARRAY DW 136,-138,133,130,-161 SUM DW ? XOR AX,AX MOV CX,LENGTHOF ARRAY MOV BX,OFFSET ARRAY AGAIN: ADD AX,ARRAY[BX] ADD BX,TYPE ARRAY LOOP AGAIN MOV SUM,AX
-
- LOOP LABEL
- 计数控制循环
- 求最大值程序
-
;eg414.asm .model small .stack .data array dw -3,0,20,900,587,-632,777,234,-34,-56 ;假设一个数组 count = lengthof array ;数组的元素个数 max dw ? ;存放最大值 .code .startup mov cx,count-1 ;元素个数减1是循环次数 mov si,offset array mov ax,[si] ;取出第一个元素给AX,用于暂存最大值 again: add si,2 cmp ax,[si] ;与下一个数据比较 jge next ;已经是较大值,继续下一个循环比较 mov ax,[si] ;AX取得更大的数据 next: loop again ;计数循环 mov max,ax ;保存最大值 .exit end
-
- 简单的加密解密程序
-
-
;eg415.asm .model small .stack .data key db 234 ;假设的一个密钥 buffer db 'This is a secret.','$' ;待加密的信息(字符串) count = sizeof buffer-1 ;不处理最后结尾字符 msg1 db 'Encrypted message: ','$' msg2 db 13,10,'Original messge: ','$' .code .startup mov cx,count ;CX=字符个数,作为循环的次数 xor bx,bx ;BX指向待处理的字符 mov al,key ;AL=密钥 encrypt: xor buffer[bx],al ;异或加密 inc bx ;指向下一个字符 cmp bx,cx jb encrypt ;没有指向最后字符,继续处理 mov dx,offset msg1 ;显示提示信息 mov ah,9 int 21h mov dx,offset buffer ;显示加密后的密文 mov ah,9 int 21h ; xor bx,bx ;BX指向待处理的字符 mov al,key ;AL=密钥 decrypt: xor buffer[bx],al ;异或解密 inc bx dec cx jnz decrypt ;等同于指令:loop decrypt mov dx,offset msg2 mov ah,9 int 21h mov dx,offset buffer ;显示解密后的明文 mov ah,9 int 21h .exit end
-
-
- 求最大值程序
- 条件控制循环
- 字符个数统计(统计以0结尾的字符串中的字符个数)
-
;eg416a.asm include io.inc .model small .stack .data string db 'Do you have fun with Assembly?',0 ;以0结尾的字符串 .code .startup xor bx,bx ;BX用于记录字符个数,同时也用于指向字符的指针 again: mov al,string[bx] cmp al,0 ;也可以使用指令“test al,al” jz done inc bx ;个数加1 jmp again ;继续循环 done: mov ax,bx ;显示个数 call dispuiw .exit end
-
- 斐波那契
-
;eg417a.asm include io.inc .model small .stack .data .code .startup mov ax,1 ;AX=F(1)=1 call dispuiw ;显示第1个数 call dispcrlf ;回车换行 call dispuiw ;显示第2个数 call dispcrlf ;回车换行 mov bx,ax ;BX=F(2)=1 again: add ax,bx ;AX=F(N)=F(N-2)+F(N-1) jc done call dispuiw ;显示一个数 call dispcrlf ;回车换行 xchg ax,bx ;AX=F(N-2),BX=F(N-1) jmp again done: .exit end
-
- 字符个数统计(统计以0结尾的字符串中的字符个数)
- 多重循环
- 冒泡排序
-
;eg418.asm .model small .stack .data array dw 587,-632,777,234,-34 ;假设一个数组 count = lengthof array ;数组的元素个数 .code .startup mov cx,count ;CX←数组元素个数 dec cx ;元素个数减1为外循环次数 outlp: mov dx,cx ;DX←内循环次数 mov bx,offset array inlp: mov ax,[bx] ;取前一个元素 cmp ax,[bx+1] ;与后一个元素比较 jng next ;前一个不大于后一个元素,则不进行交换 xchg ax,[bx+1] ;否则,进行交换 mov [bx],ax next: inc bx ;下一对元素 dec dx jnz inlp ;内循环尾 loop outlp ;外循环尾 .exit end
-
- 字符剔除(剔除空格
-
;eg419.asm .model small .stack .data string db 'Let us have a try !',0dh,0ah,'$' ;以“$”结尾的字符串 .code .startup mov dx,offset string ;显示处理前的字符串 mov ah,9 int 21h mov si,offset string outlp: cmp byte ptr [si],'$' ;外循环,先判断后循环 jz done ;为“$”结束 again: cmp byte ptr [si],' ' ;检测是否是空格 jnz next ;不是空格继续循环 mov di,si ;是空格,进入剔除空格分支 inlp: inc di ;该分支是循环程序 mov al,[di] ;前移一个位置 mov [di-1],al cmp byte ptr [di],'$' ;内循环,先循环后判断 jnz inlp ;内循环结束处 jmp again ;再次判断是否为空格(处理连续空格情况) next: inc si ;继续对后续字符进行判断处理 jmp outlp ;外循环结束处 done: mov dx,offset string ;显示处理后的字符串 mov ah,9 int 21h .exit end
-
- 冒泡排序
- 串操作指令
- 以字节、字和双字为单位多个数据存放在连续的主存区形成数据串,(也就是数组)
- 特殊的寻址方式:
- 源操作数用寄存器SI间接寻址,默认在数据段DS中,即DS:[SI],允许段超越
- 目的操作数用寄存器DI间接寻址,默认在附加段ES中,即ES:[DI],不允许段超越
- 每执行一次串操作源指针SI和目的指针DI自动±1或±2
- CLD设置方向标志DF=0 指针增加
- STD设置方向标志DF=1 指针减小
- 串传送
- 传送 MOVS
- MOVSB ES:[DI] ← DS:[SI] SI++,DI++
- MOVSW ES:[DI] ← DS:[SI] SI+=2,DI+=2
- 存储 STOS
- STOSB ES:[DI]←AL DI++
- STOSW ES:[DI]←AX DI+=
- 读取 LODS
- AL←DS:[SI] SI++
- AX←DS:[SI] SI+=2
- 重复REP 计数器也是CX
- 传送 MOVS
四、习题
4.1(2)数据的直接寻址和指令的直接寻址有什么区别?
数据的直接寻址直接给偏移地址(变量名/有效地址)
指令的直接寻址使用段内转移相对量(标号)
(5)什么是奇偶校验?
包括校验位在内的数据为为“1”的个数为奇数是奇校验。
(6)助记符JZ和JE为什么表达同一条指令?
JZ为两个数相减差值为零,JE为两个数相等。
(9)如果循环体的代码量远超过128字节,还能用LOOP指令实现计数控制循环吗?
短转移前后不能超过128字节。
(10)什么是“先循环、后判断”循环结构?
先执行一遍循环体。
4.2 判断题
(2)指令的相对寻址都是段内转移。
对。
(4)JMP指令对应高级语言的GOTO语句,所以不能使用。
错,可以使用。
(6)JA和JG指令的条件都是“大于”,所以是同一个指令的两个助记符。
错。JA判断无符号数JG判断有符号数。
(7)JC和JB指令的判断条件都是CF=1,所以是同一条指令。
错。JC前有可能是移位,JB必须是有减法操作才能判断。
(10)若CX=0,则LOOP指令和JCXZ指令都发生转移。
对,而且LOOP会执行pow(2,16)次。
4.3填空题
(2)MASM给短转移、近转移、和远转移定义的类型名依次是 SHORT 、 NEAR 、 FAR 。
(3)假设BX=1256H,字变量TABLE的偏移地址是20A1H,数据段偏移地址32F7H处存放3280H,执行指令“JMP BX"后IP= 1256H ,执行指令”JMP TABLE[BX]“后IP= 3280H 。
(4)"CMP AX,3721H"指令之后是JZ指令,发生转移的条件是AX= 3271H ,此时ZF= 1 。
(5)执行""SHR BX,1"指令后,JNC发生转移,说明BX的D0= 0 。
(6)在DX等于0时转移,可以使用指令“CMP DX, 0 ",也可以使用"TEST DX, FFFFH "构成条件,然后使用JE指令实现转移。
4.4 已知VAR1、VAR2、VAR3和VAR4是16位有符号整数,用汇编语言程序片段实现如下C语句:
VAR4=(VAR1*6)/(VAR2-7)+VAR3
;用汇编语言实现var4=(var1*6)/(var2-7)+var3 .model small .stack .data var1 dw 10 var2 dw 9 var3 dw 15 var4 dw ? .code .startup mov ax,var1 mov bx,6 mul bx mov cx,var2 sub cx,7 div cx add ax,var3 mov var4,ax add var4,30h mov dx,var4 mov ah,9 int 21h .exit end
4.7 为了验证例4-3程序的执行路径,可以在每个标号前后增加显示一个数字的功能,是的程序运行后显示数码123456.
;eg403.asm .model small .stack .data nvar dw ? fvar dd ? .code .startup labl0: jmp labl1 ;段内(短)转移、相对寻址 nop labl1: mov ah,2 mov dl,'1' int 21h jmp near ptr labl2 ;段内(近)转移、相对寻址 nop labl2: mov ah,2 mov dl,'2' int 21h mov ax,offset labl3 jmp ax ;段内转移、寄存器间接寻址 nop labl3: mov ah,2 mov dl,'3' int 21h mov ax,offset labl4 mov nvar,ax jmp nvar ;段内转移、存储器间接寻址 nop labl4: mov ah,2 mov dl,'4' int 21h jmp far ptr labl5 ;段间转移、直接寻址 nop labl5: mov ah,2 mov dl,'5' int 21h mov ax,offset labl6 mov word ptr fvar,ax mov dx,seg labl6 mov word ptr fvar+2,dx jmp fvar ;段间转移、间接寻址 nop labl6: mov ah,2 mov dl,'6' int 21h .exit end
4.13 8086处理器的指令CWD将AX符号扩展到DX。假若没有该指令,编程实现该指令功能。
;编程实现将ax符号扩展到dx .model small .stack .data .code .startup ;第一种方法:用符号扩展的含义 mov ax,45h ;初始化ax test ax,ax js one mov dx,0 jmp over1 one: mov dx,0ffffh over1: nop ;空操作,第一种方法结束 ;第二种方法:用移位实现 mov ax,0f045h ;初始化ax mov bx,ax ;为了不改变ax的值,将ax值赋值给bx shl bx,1 ;取ax的最高位到cf rcr dx,1 ;将cf的值移入dx最高位 mov cl,15 sar dx,cl ;dx算数右移15,dx所有位变成ax的符号位 .exit end
4.15 编程:先提示输入数字"INPUT NUMBER:0~9",然后再下一行显示输入的数字,结束;如果不是键入了0~9数字,就提示错误'ERROR',继续等待输入数字。
;输入数字 .model small .stack .data errormsg db 'Error!',0dh,0ah,'$' ;9个信息 msg db 'Input number(1~9): $' ;提示输入字符串 crlf db 0dh,0ah,'$' ;回车换行字符 .code .startup again: mov dx,offset msg mov ah,9 int 21h ;提示输入 mov ah,1 ;等待按键 int 21h push ax ;暂时将输入的按键字符保存到堆栈 mov dx,offset crlf ;回车换行 mov ah,9 int 21h pop ax ;恢复按键字符 cmp al,'1' ;数字 < 1? jb error cmp al,'9' ;数字 > 9? ja error mov dl,al mov ah,2 int 21h ;显示数字 jmp done;结束 error: mov dx,offset errormsg mov ah,9 int 21h ;提示输入 jmp again done: .exit end