流程转移与子程序
转移综述
背景:一般情况下指令时顺序执行的,而在实际中,常需要改变程序的执行流程。
转移指令
1)可以控制CPU执行内存中某处代码的指令
2)可以修改IP,或同时修改CS和IP的指令
转移指令分类
1)转移行为
段内转移:只修改IP,如:jmp ax
段间颛臾:同时修改cs和IP,如jmp 1000:0
2)根据指令对IP修改的范围不同
段内短转移:IP修改范围:-128-127
段内长转移:IP修改范围:-32768-32767
3)转移指令
无条件转移指令,如:jmp
条件转移指令,如jcxz
循环指令,如:loop
过程
中断
操作符OFFSET
格式:offset 标号
实例
assume cs:code
code segment
start:
mov ax,offset start ;相当于mov ax,0
s:mov ax,offset s ;相当于mov ax,3
mov ax,4c00H
int 21H
code ends
end start
利用offset指令,将s处的两条指令复制到s0处
assume cs:code
code segment
s:mov ax,bx
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0:nop ;空指令占位符
nop ;空指令占位符
mov ax,4c00H
int 21H
code ends
end
jmp指令
jmp指令功能
无条件转移,可以值修改IP,也可以修改cs和ip
jmp指令要给出两种信息
1)转移的目的地址
2)jmp指令要给出两种信息
段间转移(远转移):jmp 2000:1000
段内短转移:jmp short 标号,IP的修改范围为-128-127,8位的位移
段内近转移:jmp near ptr 标号,IP的修改范围为-32768-32767,16位的位移
观察地址偏移实例
assume cs:code
code segment
start:
mov ax,0
jmp short s
add ax,1
s:inc ax
mov ax,4c00H
int 21H
code ends
end start

1)跳转到076A:0008的位置继续执行命令
2)观察跳转指令的机器码可以观察到03,是因为需要在IP原有的数值上进行更改,在执行跳转指令时,ip已经指向了跳转指令的下一条指令
3)IP的值也就是0005,在此基础上添加03,刚好是所要跳转的目的地0008
两种段内转移
短转移:jmp short 标号
功能:ip = ip + 8位位移
原理:
1)8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
2)short 指明此处的位移为8位位移
3)8位位移的范围为-128~127,用补码表示
4)八位位移由编译程序在编译时算出
近转移:jmp near ptr 标号
功能:ip = ip + 16位位移
原理:
1)16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
2)near ptr 指明此处的位移为16位位移
3)8位位移的范围为-32768~32767,用补码表示
4)十六位位移由编译程序在编译时算出
远转移:jmp far ptr 标号
功能:直接指明了跳转的目的地址,包含了段号的段地址CS和偏移地址IP
转移地址在内存中的jmp指令
1)段内转移
功能:jmp word ptr 内存单元地址
原理:从内存单元地址处开始存放着一个字,是转移的目的偏移地址
2)段间转移
功能:jmp dword ptr 内存单元地址
原理:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的地址,低地址处是转移的目的偏移地址
jmp指令小结
| jmp指令格式 | 示例 |
|---|---|
| jmp 标号 | 段间转移(远转移):jmp far ptr 标号 段内短转移:jmp short 标号 段内近转移:jmp near ptr 标号 |
| jmp 寄存器 | jmp bx jmp ax 等等 |
| jmp 内存单元 | 段内转移:jmp word ptr 内存单元地址 段间转移:jmp dowrd ptr 内存单元地址 |
其他转移指令
jcxz指令
格式:jcxz 标号
功能:如果cx = 0,则转移到标号处,否则不进行跳转
jcxz是有条件转移指令
1)所有的有条件转移指令都是短转移
2)对IP的修改范围都是-128~127
3)在对应的机器码中包含转移的位移,而不是目的地址
同理:loop指令也是属于短程的跳转指令
call与ret指令
调用子程序:call
返回:ret
示例
mov ax,0
call s
mov ax,4c00H
int 21H
s:add ax,1
ret
call指令
字面意思:调用子程序
实质:流程转移,call指令实现转移的方法与jmp指令的原理相似
格式一:call 16位寄存器
cpu执行call指令,进行两部操作:
1)将当前的IP压入栈中
sp = sp - 2
ss * 16 + sp = ip
2)ip = 标号所在的偏移地址
call标号:
1)16位位移 = 标号处地址 - call指令后的第一个字节的地址
2)16位位移的范围为-32768~32767,用补码表示
3)16位位移由编译程序在编译时算出
格式二:call far ptr 标号
cpu执行call指令,进行两部操作:
1)将当前的cs与ip压栈
sp = sp - 2
ss * 16 + sp = cs
sp = sp - 2
ss * 16 + sp = ip
2)cs = 标号所在段的段地址
ip = 标号所在的偏移地址
格式三:call word ptr 内存单元地址
相当于:
push IP
jmp word ptr 内存单元地址
汇编代码:
mov sp,10H
mov ax,0123H
mov ds:[0],ax
call word ptr ds:[0]
执行后,ip = 0123H,sp = 0EH
格式四:call dword ptr 内存单元地址
相当于:
push CS
push IP
jmp dword ptr 内存单元地址
汇编代码:
mov sp,10H
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执行后,cs = 0,ip = 0123H,sp = 0CH
返回指令
| 指令名称 | ret指令 | retf指令 |
|---|---|---|
| 功能 | 用栈中的数据,修改IP的内容,从而实现近转移 | 用栈中的数据,修改CS和IP的内容,从而实现远转移 |
| 相当于 | pop IP | pop IP pop CS |
ret实例
assume cs:code,ss:stack
stack segment
db 16 dup (0)
stack ends
code segment
moc ax,4c00H
int 21H
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
retf实例
assume cs:code,ss:strack
strack segment
db 16 dup (0)
strack ends
code segment
mov ax,4c00H
int 21H
start:
mov ax,strack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
call和ret配合使用
assume cs:code
code segment
main:
....
call sub1 ;调用子程序
....
mov ax,4c00H
int 21H
sub1:
....
call sub2 ;调用子程序
...
ret
sub2:
....
ret
code ends
end main
计算2N(以三次方为例)
assume cs:code
code segmeng
main:
mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00H
int 21H
s:
add ax,ax
loop s
ret
code ends
end main
上述程序在运行时,使用的栈段不是我们人为定义的,是在内存中随机开辟的空间,因此具有一定的危险性。
mul惩乘法指令
命令格式
mul 寄存器
mul 内存单元
| 八位乘法 | 十六位乘法 | |
|---|---|---|
| 被乘数 | AL | AX |
| 乘数 | 八位寄存器或内存字节单元 | 十六位寄存器或内存字单元 |
| 结果 | AX | DX(高位)和AX(低位) |
计算100 * 10
mov al,100
mov bl,10
mul bl
;结果:ax = 03E8H
计算100 * 10000
mov ax,100
mov bx,10000
mul bx
;结果:dx = 00FH,ax = 4240H
汇编语言的模块化程序设计
模块化程序设计
assume cs:code
code segment
main:
....
call sub1 ;调用子程序
....
mov ax,4c00H
int 21H
sub1:
....
call sub2 ;调用子程序
...
ret
sub2:
....
ret
code ends
end main
子程序:根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
参数和结果传递的问题
问题:提供的N,计算N的三次方
考虑:我们将参数N存储到什么地方,计算得到的值,存储到什么地方
方案:
1)用寄存器传递参数
2)用内存单元进行参数传递
3)用栈传递参数
用寄存器传递参数
问题:根据提供的N,计算N的3次方
考虑:将参数N存储在什么地方?计算得到的数字数值,存储在什么地方?
用寄存器传递参数
参数存放到bx中,即bx = N
子程序中用多个mul指令计算N3
将结果放到dx和ax中:dx:ax = N3
编程任务
assume cs:code,ds:data
data segment
dw 1,2,3,4,5,6,7,8
dd 8 dup (0)
data ends
code segment
main:
mov ax,data
mov ds,ax
mov si,0 ;将源地址指针寄存器指向数据段
mov di,16 ;将目的地址指针寄存器指向存储段
mov cx,8 ;设置循环次数
l:
mov bx,[si]
call cube
mov [di],ax
mov [di + 2],dx
add si,2
add di,4
loop l
mov ax,4c00H
int 21H
cube: ;计算三次幂的子程序
mov ax,bx
mul bx
mul bx
ret
code ends
end main
上述传递方法的缺点,由于寄存器的个数是有限的,因此不能一次性传递多个数据。
用内存单元批量传递数据
方案:
1)将批量数据放到内存中,然后将他们所在内存空间的首地址放到寄存器中,传递给需要的子程序
2)对于具有批量数据的返回结果,也可用同样的放大
编程:将data段中的字符串转化为大写
assume cs:code,ds:data
data segment
db 'conversation' ;十二个字符,占用空间12字节
data ends
code segment
main:
mov ax,data
mov ds,ax
mov si,0
mov cx,12
call fun
mov ax,4c00H
int 21H
fun:
and byte ptr [si],11011111b
inc si
loop fun
ret
code ends
end main
上述程序完成了将小写字母变为大写字,并且可以发现,call跳转与loop跳转可以跳转到同一标志符上。
用栈传递参数
原理:有调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数
任务:计算(a-b)^3,a、b为word型数据
1)进入子程序前,参数a、b入栈
2)调用子程序,将使栈顶存放IP
3)结果:(dx:ax) = (a - b)^3
例:设a = 3,b = 1,计算:(a - b)^3
code segment
main:
mov ax,1
push ax
mov ax,3
push ax
call difcube
mov ax,4c00H
int 21H
difcube:
push bp
mov bp,sp
mov ax,[bp + 4]
sub ax,[bp+6]
mov bp,ax
mul bp
mul bp
pop bp
ret 4
code ends
寄存器冲突的问题
会造成冲突的汇编代码
assume cs:code,ds:data
data segment
db 'asdasd',0
db 'zxczcx',0
db 'cvbcv',0
db 'sdfsdfsd',0
data segment
code segment
main:
mov ds,ax
mov bx,0
mov cx,4
l:
mov si,bx
call capital
add bx,5
loop l ;<=======================
mov ax,4c00H
int 21H
capital:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111n
inc si
jmp short capital
ok: ret
code ends
end main
在最后一次循环结束时,cx的值已经变为0,但是在第18行代码处,进行loop循环时,先将cx自行减一,然后判断cx是否为0,由于当时的cx已经为0,再次减一就变为了FFFF,那么将进入错误的逻辑。
解决冲突的基本操作
子程序框架:在子程序的开始,将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复
子程序开始:
子程序使用的寄存器入栈
子程序内容
子程序使用的寄存器出栈
返回(ret、retf)
标志寄存器(PWD)
标志寄存器的结构
1)flag寄存器是按位起作用的,也就是说,他的每一位都有专门的含义,记录特定的信息
2)8086CPU中没有使用flag的1,3,5,12,13,14,15位,这些位不具有任何意义。
标志寄存器的作用
1)用来存储相关指令的某些执行结果
2)用来为CPU执行相关指令提供行为依据
3)用来控制CPU的相关工作方式
观察寄存器的值

| 寄存器名称 | 标志 | 值为1时 | 值为0时 | 意义 | 所在位 |
|---|---|---|---|---|---|
| overFlow | OF | OV | NV | 溢出 | 11 |
| direction | DF | DN | UP | 方向 | 10 |
| sign | SF | NG | PL | 符号 | 7 |
| zero | ZF | ZR | NZ | 零值 | 6 |
| parity | PF | PE | PO | 奇偶 | 2 |
| carry | CF | CY | NC | 进位 | 0 |
ZF标志
1)ZF标记记录了相关指令的计算结果是否为0
ZF = 1,表示结果是0
ZF = 0,表示结果不是0
2)示例
;结果为0
mov ax,1
and ax,0
;结果非零
mov ax,1
or ax,0
3)小总结
- 在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如:add、sub、mul、div、inc、or、and等,他们大都是运算之灵,进行逻辑或算数运算
- 有的指令的执行对寄存器是没有影响的,比如:mov、push、pop等,他们大都是传送指令
- 使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的那些标志位造成影响
PF奇偶标志
1)PF记录指令执行后,结果的所有二进制位中1的个数
1的个数为偶数,PF = 1
1的个数为奇数,PF = 0
2)示例
;PF = 0
mov al,1
add al,10
;PF = 1
mov ,1
or al,2
SF符号标志
1)SF记录指令执行后,将结果视为有符号数
结果为负,SF = 1
结果非负,SF = 0
2)SF标志是CPU对有符号数运算结果的一种记录。将数据当做有符号数来运算的时候,通过SF可知结果的正负;将数据当做无符号数来运算的时,SF的值则没有意义,虽然相关的指令影响了它的值。
CF进位标志
1)在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高位的进位值,或从更高为的借位值。
2)CF记录指令执行后
有进位或借位:CF = 1
无进位或借位:CF = 0
3)示例
;运算结果:al = 30H,CF = 1
mov al,98H
add al,al
;运算结果:al = 60H,CF = 0
add al,al
;运算结果:al = C8H,CF = 1
sub al,98H
OF溢出标志
1)在进行有符号运算的时候,如果结果超出了机器所能表示的范围,称为溢出
2)OF记录有符号数操作指令执行后
有溢出:OF = 1
无溢出:OF = 0
3)注意:溢出只是对有符号数运算而言
OF与CF的区别
1)OF是对有符号数运算有意义的溢出标志位
2)CF是对无符号数运算有意义的进/借为标志位
1.最高位有进位,一定发生溢出。
错!
如8位加法:
FF+02=01
(CF)=1,(OF)=0
如理解为符号加法,则为-1+2=1,(CF)=1表示有进位,(OF)=0表示无溢出,此例可推翻命题
如理解为无符号加法,则为127+2=1,(CF)=1表示有进位,亦表示溢出,(OF)无意义,
2.发生了溢出,最高位一定有进位。
错
8位加法:
40+40=80
(CF)=0,(OF)=1
如理解为符号加法,则为64+64=-128,(CF)=0表示无进位,(OF)=1表明结果溢出,此例可推翻命题
如理解为无符号加法,则为64+64=128,(CF)=0表示无进位,亦表示无溢出,(OF)无意义,
测试表格
| 指令 | CF | OF | SF | ZF | PF |
|---|---|---|---|---|---|
| sub al,al | 0 | 0 | 0 | 1 | 1 |
| mov al,10H | 0 | 0 | 0 | 0 | 0 |
| add al,90H | 0 | 1 | 1 | 0 | 1 |
| mov al,80H | 1 | 0 | 0 | 0 | 0 |
| add al,80H | 0 | 1 | 1 | 0 | 1 |
| mov al,0FCH | 1 | 0 | 1 | 0 | 1 |
带进/借位的加减法
adc带进加法指令
adc是带进位加法指令,它利用了CF为上记录的进位值
格式:adc 操作对象1,操作对象2
功能:操作对象1 + 操作对象2 + CF
adc指令应用:大数相加
问题:8086CPU提供add指令,完成8位或16位加法,有更大的数相加时,如何做
例子:编程实现1EF000H + 201000H,结果存放在ax(高16位)和bx(低16位)中
mov ax,001EH
mov bx,0F00H
add bx,1000H
adc ax,0020H
更大数值的进位同理
sub ax,ax与mov ax,0的区别
前者可影响CF标志位,后者对标志位无影响,如果需要将CF清零,需要用sub命令
inc di与add di,2的区别
前者不影响标志位,后者运算结束后影响标志位
sbb指令:带借位减法指令
1)格式:sbb 操作对象1 操作对象2
2)功能:操作对象1 = 操作对象1 - 操作对象2 - CF
3)与sub区别:利用CF为上记录的借位值
4)比如:sbb ax,bx
5)实现功能:ax = ax - bx - CF
cmp和条件转移指令
CMP指令
1)格式:cmp 操作对象1,操作对象2
2)功能:计算操作数对象1 - 操作对象2
cmp是比较指令,功能相当于减法指令,只是不保存结果
cmp指令执行后,将对标志寄存器产生影响
| 比较关系 | ax ? bx | ax - bx 特点 | 标志寄存器 |
|---|---|---|---|
| 等于 | ax == bx | ax - bx = 0 | ZF = 1 |
| 不等于 | ax != bx | ax - bx != 0 | ZF = 0 |
| 小于 | ax < bx | ax - bx 将产生错位 | CF = 1 |
| 大于等于 | ax >= bx | ax - bx不必错位 | CF = 0 |
| 大于 | ax > bx | ax - bx既不借位,结果又不为0 | CF = 0且ZF = 0 |
| 小于等于 | ax <= bx | ax - bx 或者借位,或者结果为0 | CF = 1 或 ZF = 1 |
条件转移指令
应用格式
cmp oper1,oper2
jxxx 标号
| 指令 | 含义 | 测试条件 |
|---|---|---|
| je/jz | 相等/结果为0 | ZF = 1 |
| jne/jnz | 不等/结果不为0 | ZF = 0 |
| js | 结果为负 | SF = 1 |
| jns | 结果非负 | SF = 0 |
| jo | 结果溢出 | OF = 1 |
| jno | 结果溢出 | OF = 0 |
| jp | 奇偶位为1 | PF = 1 |
| jnp | 奇偶位为0 | PF = 0 |
| jb/jnae/jc | 低于/不高于等于/有借位 | CF = 1 |
| jnb/jae/jnc | 不低于/高于等于/无借位 | CF = 0 |
上述指令代表的含义:
| 简写 | 全写 | 简写 | 全写 |
|---|---|---|---|
| J | Jump | G | Greater |
| E | Equal | S | Sign |
| N | Not | C | Carry |
| B | Below | P | Parity |
| A | Above | O | Overflow |
| L | Less | Z | Zero |
利用cmp配合跳转指令实现高级语言中的IF
例:如果ah = bh,则ah = ah ,否则ah = ah + bh
cmp ah,bh
je s
add ah,bh
jmp short ok
s:
add ah,ah
ok:
ret
条件转移指令的应用
统计数据域中数值为8的字节的个数
code segment
main:
mov ax,data
mov ds,ax
mov bx,0
mov axx,0
mov cx,8
s:
cmp byte ptr [by],8
jne next
inc ax
next:
inc bx
loop s
mov ax,4c00H
int 21H
code ends
DF标志和串传送指令
DF方向标记位
功能:
1)在串处理指令中,控制每次操作后si,di的增减
2)DF = 0:每次操作后si、di递增
3)DF = 1:没出操作后si、di递减
对DF位进行设置
1)cld指令:将标志寄存器的DF设为0(clear)
2)std指令:将标志寄存器的DF设为1(setup)
串传送指令
传送指令1:movsb
功能:(以字节为单位传送)
1)se * 16 + di = ds * 16 + si
2)如果df = 0
si = si + 1
di = di + 1
如果df = 1
si = si - 1
di = di - 1
传送指令2:movsw
功能:(以字为单位传送)
1)se * 16 + di = ds * 16 + si
2)如果df = 0
si = si + 2
di = di + 2
如果df = 1
si = si - 2
di = di - 2
因此,将字符串赋值操作代码进行改进
assume cs:code,ds:data
data segment
db 'welcome to masm!'
db 16 dup (0)
data ends
code segment
main:
;设置寄存器相关初值
mov ax,data
mov ds,ax
mov si,0
mov es,ax
mov di,16
cld ;将df标记位初始化为0
;循环传送
mov cx,16
l:
movsb
loop l
mov ax,4c00H
int 21H
code ends
end main
rep指令
1)rep指令常和串传送指令搭配使用
2)功能:根据cx的值,重复执行后面的指令
3)用法:rep movsb
;循环传送
mov cx,16
l:
movsb
loop l
修改后:
mov cx,16
l
rep movsb
loop l

浙公网安备 33010602011771号