汇编 -- 分支程序设计

1 转移指令:条件转移和无条件转移

2 条件转移指令:

条件转移指令


JXX 标号


单标志条件转移:


JC (JNC)
有(无)进位则转  CF=1(CF=0)
JP (JNP) 1个数为偶数则转 PF=1
JZ (JNZ) 为零则转 ZF=1
JS (JNS) 为负数则转 SF=1
JO (JNO) 溢出则转 OF=1
无符号数专用:


JA (JBE) >  (JBE<=) CF=0 && Zf=0
JA与JBE相反
JB (JAE) <  (JAE>=) CF=1 JB与JAE相反
有符号数专用:      
JG (JLE) > SF=OF且ZF=0 JG与JLE相反
JL (JGE) < SF!=OF且ZF=0  

附上ADD对标志位的影响:(SUB与ADD基本一致,CMP除不更改目的操作符之外,与SUB一致)
OF 字节运算结果超出字节有符号数的范围(-128~+127)或字运算超出字有符号数范围(-32768~+32767)时,OF=1否则OF=0。把操作数视为有符号数时,可以通过此标志了解是否溢出。
SF 运算结果的最高位为1时,SF=1,否者SF=0 (SF与结果的最高位一致)
ZF 运算结果为0时,ZF=1,否则=0
AF 运算结果时,D3向D4产生进位时AF=1,否则AF=0
PF 运算结果中1的个数为偶数时PF=1,否则=0
CF 运算结果的最高位产生进位时,即字节运算结果超出字节无符号数范围(0~65535)或者字运算结果超出0~65535时,CF=1,否则=0,可以通过此标志了解无符号运算符的溢出情况。

再附上各个标志:
溢出标志OF(Over flow flag)                             OV(1)                         NV(0)

方向标志DF(Direction flag)                             DN(1)                          UP(0)

中断标志IF(Interrupt flag)                                EI(1)                           DI(0)

符号标志SF(Sign flag)                                     NG(1)                          PL(0)

零标志ZF(Zero flag)                                        ZR(1)                         NZ(0)

辅助标志AF(Auxiliary carry flag)                      AC(1)                          NA(0)

奇偶标志PF(Parity flag)                                  PE(1)                          PO(0)

进位标志CF(Carry flag)                                  CY(1)                         NC(0) 

mov al, 76h
cmp al, 80h
jnz lab2 ;成立
 
mov bl, 38h
SHL bl,1 ;D7 move to CF
JC LAB2 ;不成立
SHL等同SAL,低位进0,高位进CF,在本次学习中D0到D7用来表示一个字节从最低位到最高位。
   

关于有符号数和无符号数
其实很简单理解,这里写出来以防那天糊涂了看下:
有符号数大小的比较标准与无符号数不同;
对于无符号数:每一位均为数值位。
对于有符号数:在机器中以补码表示,其最高位为符号数。当最高位为0时,正数,其余位为数值;最高位为1时,负数,所有位取反加一方为数值。
以单字节数为例:
无符号数的大小关系为: 00h(1)-> 01h -> FFh (255)
有符号数大小关系为:
80h(-128)->81h(-127)->...->FFh(-1)->00h(0)->...7Fh (127)

因此:
mov al, 76h
cmp al, 80h
JL
 是不成立的,76这个时候作为负数是大于80h的,不要混淆,一定要看清楚是有符号数跳转还是无符号。

2.2  条件转移中标号的限制:
【1】80386以上的指令集:只能是近标号(统一代码段);
【2】8086和80286的只能是短标号(不仅必须处于同一代码段,而且相距的字节数在 128~+127之间。(但是不需要加短转移标识符SHORT). 当需要转移的目的地超过上述限制时,就必须结合使用无条件转移指令。

3 无条件转移指令
JMP 目的位置
远近是看是否在同一代码段;直接和间接是看是否使用标号(还是偏移地址)
指令 功能
JMP SHORT 标号 转移到指定标号处,该标号与该指令不仅须处于同一代码段,而且相距字节数在-128和127之间,即实现短转移
JMP 近标号 同一代码段(直接近转移)
JMP 远标号 不处于同一代码段(直接远转移)
JMP MEM16/REG16 由16位存储器操作数或者寄存器操作数间接给出目的地址的偏移地址,实现间接转移。
JMP MEM32 由32位存储器操作数间接给出目的位置的偏移地址和段地址,实现间接转移。
JMP REG32 此为80386以上指令集的指令,由32位寄存器操作数间接给出目的地址的偏移地址,实现间接近转移。

3.1 无条件转移使用场合(三个):
1)配合条件转移指令实现条件远转移    
CMP AL, 80H
JB LAB2
如果: JB LAB2 中的标号Lab2与条件转移指令不再同一代码段,就会出错,可以改为:
CMP AX, 80h
JAE LAB1
JMP LAB2       
就是用一个相反的指令来铺垫,然后接着JMP到远标号。
2)避免一个分支“滑入”另一个程序分支    
以下面的例子为例:
将有符号数X和Y中的较大者送变量Z,试写出程序段。
X DB 60H
Y DB 94H
Z DB ?
...
mov al, x
cmp al, y
JL YG; AL mov Z,AL

YG: mov BL, Y
mov BL, Z

OK:
此例中,正常来说,MOV X,AL执行完毕后,就应该转入
OK了,但是如果按照左边代码的话,代码却会顺序执行到YG,
因此需要无条件转移JMP
JMP OK。
X DB 60H
Y DB 94H
Z DB ?
...
mov al, x
cmp al, y
JL YG; AL mov Z,AL
JMP SHORT OK
YG: mov BL, Y
mov BL, Z

OK:
3)实现多分支程序结构 JMP不想条件转移指令,它的目的操作数可以是存储器也可以是寄存器,因此
可用来构建多分枝程序。
 
下面是个实例,将分支的标号放入一个数组
中,然后利用数组来引导到对应分支。

例子1:

BASE DW ZERO, ONE, TWO, ..., N
...
JMP BASE[BX]
... ...
ZERO: ... ...
   JMP  OK
ONE: ... ...
   JMP OK
... ...
N: ...
OK:
...

例子2:
直接利用cx值来转移

JMP CX
JMP ECX
说明:
1 近转移的实质在于:改变IP(EIP)总是指向当前代码段下一顺序指令的顺序,而只想目的位置处的指令,即把目的位置的偏移地址送IP或EIP。
【区别:远转移还要送段地址到CS】

2
 近转移指令一般用于向下转移,即目的位置的偏移地址>本转移指令的偏移地址,有时也可用于向上转移。

3 转移指令本身不影响状态标志;

4 在需要根据两个数的比较结果确定流向时,应根据实际问题所设计的数据来选择是无符号数,还是有符号数来选用对应的条件转移指令。

5 有些条件转移指令的助记符有其他表示形式
 


7.3 分支程序设计:
两种设计方法:
1 测试法分支程序设计: 选用影响状态标志的指令和条件转移指令
2 跳转表法分支程序设计: 间接寻址的无条件转移指令

7.3.1 测试法
测试法  
常用影响状态标志的指令:CMP  
例子1:求Z=|X-Y| (X,Y有符号数)
name example_7.3.1
dseg segment
x db 40h
y db 73h
z db ?
dseg ends

sseg segment stack
db 80h dup(0)
sseg ends

cseg segment
     assume cs:cseg, ds:dseg, ss:sseg
start:
mov ax, dseg
mov ds, ax
xor ax, ax

mov al, x
cmp al, y
jl xl
jmp yl

xl:
mov bl,y
sub bl,al
mov z,bl
jmp ok


yl:  ;如果只有两个分支,红色部分常防止在jl xl后,而不用jmp。
sub al,y
mov z, al


ok:
mov ah, 4ch
int 21h

cseg ends
end start
例子1的另外一种写法:
直接求差值,如果符号是负数,则直接取反,然后再转移。
mov al, x
sub al,y
jns xg
NEG AL ;
 取反

xg:
mov z, al
例子:7.11
根据键盘输入的0,1,2,3分别显示信息"8086" "80386", "pentium"
难点:
1 键盘上的0,1,2,3转换为数值1,2,3(也可以不转,但要明白其ASCII码与数值的区别,直接用ASCII码来判断当然也可以)
如果利用 测试法实现如下:  
mess0 db '8086','$'
...
mov ah, 07h
int 21h

and al, 0fh ;将对应字符的ascii码转换为0,1,2,3                                                                                                                                    

cmp al,0
jz M0

cmp al,1
jz M1

CMP al,2
jz M2

CMP al,3
jz M3


m3:
mov dx, offset mess3
jmp short disp

m2:
...

disp:
mov ah, 09h
int 21h
;
 
例子7.12 将Blks为首址的连续N个字节数传送至以BLKD为首址的存储区                  分析问题:
1 难点是数据块的相对位置
  • blks与blkd不重叠,从哪里开始都可以
  • blks与blkd部分重叠,且在blkd的前面(即blks的地址小于blkd):此时只能从底部开始传输
  • blks与blkd部分重叠,且在blkd的后面(即blks的地址大于blkd):此时只能从头部开始传输
2 怎么处理数据传送问题:常用方法 使用编制寄存器 SI, DI (SI指向源,DI指向目的), SI, DI不断加1, 而次数用CX控制(后面的循环结构也是这样的),CX要传输一次就减1.
name example_7.3.1
dseg segment
ORG $+24h
qiaoString db 'happy national day',0ah, 0dh, '$'                                                                                       
N EQU $-qiaoString ; used to get the strs' length
BLKS DW qiaoString
BLKD DW qiaoString+5


dseg ends

sseg segment stack
db 80h dup(0)
sseg ends

cseg segment
     assume cs:cseg, ds:dseg, ss:sseg
start:
mov ax, dseg
mov ds, ax
xor ax, ax

;.................

mov dX, blks
mov ah, 09h
int 21h

;

mov cx, N
mov SI, BLKS
mov DI, BLKD

mov BX,1 ; uesd to as a statue, and can join add to replace INC

cmp si, di
ja move

add SI, CX
DEC SI
add DI, CX
DEC DI

NEG bx
ja move ; not userful, just to make a notification

move:
mov al, [SI]
mov [DI], al
add SI, bx
add SI, bx
dec cx

jnz move; end_move

; output the string:

mov dX, blks
mov ah, 09h
int 21h

mov dX, blkd
mov ah, 09h
int 21h





;.................                                                                                            



ok:
mov ah, 4ch
int 21h

cseg ends
end start


                                                 
org : 让IP从后面数值处开始

$: 当前位置的偏移地址

SI, DI , CX, BX 的专业用法

DEC, INC

NEG

在处理显示字符串时,出了3个错误:
  1. DX写错成了Bx,出来的全是乱码
  2. 如果输出qiaoString,就要写offset,但是BLKS,BLKD本身就是偏移地址,不需要再写Offset了
  3. qiaoString定义时用DB 是表示后面的变量可以是字节类型可以定义的,如果是字符串,就拆开一个一个表示。但BLKS DW qiaoString为什么用DW而DB不行呢,这里说明BLKS是被qiaoString的偏移地址赋值的(所有一般使用DW,以防DB表示不了)这是DW伪指令中包含的(DB没有包含地址表达式的表达对象)。

跳转表法:
1 分支地址表法 思想:通过dw指令将n个分支入口处的偏移地址BRi(i=0,1,***, n-1)
按序列放在一段连续的存储区中,构成跳转表。

设改存储区的首地址为BASE,则第i个分支入口处的偏移地址的存放
地址则为BASE+2*i,程序需要转向时,只需将2*i送BX,使用指令:
JMP BASE[BX]即可。
【其实我们前面得到立方数的例子时就是这么做的】
 
重新实现7.11    
改变体现在两个地方:    
1) 数据区: DSEG SEGMENT
BASE DW M0 ; 多分支入口处偏移地址构成的跳转表
        DW M1
        DW M2
        DW M3
 
2) 下文调用时 SHL AL,1 ;二倍
CBW
mov bx, ax
jmp base[bx]

m0:...
m1:...
 
2 转移指令表法 与分支地址表法的区别:(多直接啊,呵呵)
  • 构成跳转表的不是分支入口处的偏移地址,而是:转向n个分支的无条件转移指令
 
举个例子: 此时,跳转表没有放在数据段,而是放在了代码段。
MOV bx, offset BASE
add bx, ax
jmp BX

base:
 jmp short M0 ;
 jmp short M1
 ...
 

本章注意:

1 要为每个分支安排出口,避免滑入其他入口
2 多分支的宜选择 跳转表法
3 应该不同实验数据来检查每一个分支都正确。





























你可能感兴趣的:(汇编)