8086汇编语⾔学习(七)8086跳转指令
8086跳转指令
⽬前为⽌,我们的程序的指令执⾏都是线性的,从上到下,由CPU⾃动的增加IP的值,顺序的执⾏指令。但对于复杂的需求,只有线性的指令执⾏⽅式是远远不够的。
对于⾼级语⾔,有着如if/else的逻辑跳转分⽀,如for/while的循环结构,还有函数⼦程序的调⽤与返回等等。正是有了这些能够控制程序执⾏指令的不同⽅式,才能具有⾜够的表达能⼒,满⾜⾜够复杂的需求,成为⼀门图灵完备的语⾔。那么上述的逻辑跳转、循环,在基于图灵机的CPU硬件上是如何实现的呢?通过8086汇编的跳转指令的学习,我们得以⼀窥究竟。
CPU是通过CS:IP来获取下⼀条指令的值,那么通过指令修改CS、IP这两个寄存器的值,便可以控制CPU所执⾏的指令了。可由于控制CPU执⾏指令的CS、IP⼗分的关键,因此8086并不允许像其它普通的寄存器⼀般使⽤mov等指令对CS、IP修改(mov IP,1000H是⾮法的),⽽是提供了专门的指令来控制CS、IP的值,这⼀类指令被称为8086跳转指令。
跳转指令按照类型可以分为五种:⽆条件跳转指令、有条件跳转指令、循环指令、过程调⽤与返回指令以及中断指令。
⽆条件跳转指令(jmp)
jmp既可以只修改IP,也可以同时修改CS和IP。作为跳转指令,在编程时需要指定跳转的位置,进⽽修改CS/IP的值。
段内转移
段内短转移(IP 变化-128~127):段内短转移的格式为 jmp short [标号]。
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
段内近转移(IP 变化-32768~32767):当所要跳转的间隔⼤于短转移的时候,就需要使⽤段内近转移。段内近转移和短转移类似,格式为 jmp near ptr [标号]。
段内转移只修改IP,不修改CS的值。
段间转移
当跳转的间隔超过了段内近转移的限制时,就需要使⽤段间转移了。段间转移的格式为jmp far ptr [标号]。和内存寻址⼀样,jmp指令所要跳转的位置也可以通过寄存器或是指令中的⽴即数指定。
jmp寄存器跳转
jmp [16位寄存器] 例如 jmp ax,寄存器跳转属于段内跳转。
jmp内存跳转
jmp word ptr [内存单元地址] 例如: jmp word ptr 2345H,jmp word ptr [bx] ,[]内只要是符合内存寻址⽅式的语法皆可。jmp word ptr处理的是16位数,属于段内转移。
jmp dword ptr [内存单元地址] jmp dword ptr和jmp word ptr类似,只不过会将对应地址的处的两个字/四字节的数据作为偏移地址,其中IP等于指定的内存地址,CS等于指定的内存地址+2(⽰例)。jmp dword ptr处理的是32位数,属于段间转移。
跳转指令原理
就转移指令的实现原理来看,段内转移是通过相对地址偏移量来控制的。段内短转移可以使得IP偏移2^8的范围,即(-128~127),⽽段内近转移可以使得IP偏移2^16的范围,即(-32768~32767)。
8086的CPU是16位的,在20位的寻址范围内进⾏更⼤幅度的跳转,16位的偏移地址是不够的,因此段间转移的指令是通过绝对地址来实现的。
虽然理论上段内转移都可以使⽤段间转移来实现,但是由于不同的跳转指令所占⽤的内存空间是不⼀样的(段内短转移=8位指令+8位偏移地址=16bit,段内近转移=8位指令+16位偏移地址=24bit,段间转移=8位指令+16位段地址+16位偏移地址=40bit)。所以编程时,在满⾜需求的前提下还是尽可能的使⽤更简单,更节约内存的⽆条件跳转指令,提⾼效率。
jmp是最直接的⽆跳转指令,类似于C语⾔的goto。对于喜欢结构化编程的⼈来说,goto的跳转过于灵活很容易使得⼤项⽬中代码变得晦涩混乱,但是汇编程序所构建的项⽬不会特别⼤,jmp还是⾮常直
接和⽅便的。(从另⼀个⾓度看,正是因为汇编语⾔的抽象能⼒不够强,导致很难构建出⾜够⼤型、复杂同时还很可靠的程序)
有条件跳转指令(jcxz)
jcxz(jmp if CX is zero)有条件跳转指令,类似于段内短跳转jmp short,所能变化的ip范围同样为(-128~127)。格式为 jcxz [标号]。唯⼀的不同在于,只有当满⾜条件寄存器cx=0时,才会进⾏跳转,否则就和正常情况⼀样IP⾃增,按顺序执⾏下⼀条指令,这也是jcxz被称为有条件跳转指令的原因(只有满⾜条件才进⾏跳转)。
需要特别注意的是,通⽤寄存器ax/bx/cx/dx并不是完全等价的,在某些场合下会具有⼀些特别的作⽤,例如上述jcxz便依赖寄存器cx。其作⽤可以从寄存器的全名中可见⼀斑,ax/bx/cx/dx并不是英⽂字母abcd的简称,⽽分别是accumulate-register累加寄存器、based-register 基地址寄存器、count-register计数寄存器、data-register 数据寄存器。
ax accumulate-register累加寄存器:ax⼀般⽤于存放算术、逻辑运算中的操作数或结果。同时I/O指令也都需要使⽤ax与外设接⼝传递数据。
bx based-register基地址寄存器:bx⼀般⽤于存放访问内存时的地址。在8086内存寻址时,指定偏移地址时有提到过。
cx count-register计数寄存器:cx⼀般⽤于有条件跳转、循环、串操作指令。jcxz和loop循环等指令都依赖于cx寄存器。
dx data-register 数据寄存器:dx⼀般⽤于寄存器间接寻址中的I/O指令中存放I/O端⼝的地址。
循环指令(loop)
循环指令同样依赖寄存器cx。格式为loop [标号]。loop指令的语义是,⾸先将cx⾃减1,如果cx不为0,则跳转⾄标号处。否则什么也不做,离开循环,顺序执⾏下移。
循环指令的跳转范围和有条件跳转指令⼀样,ip的变化范围为(-128~127)。汇编语言跳转指令
⽤C风格的伪代码表⽰为:
cx--;
if(cx == 0){
jmp short【标号】
}else{
顺序执⾏下⼀条指令
}
过程调⽤/返回以及中断指令(call/ret、int等)
过程调⽤以及CPU处理硬件中断时,同样涉及到了程序执⾏指令的跳转,分别对应了过程调⽤/返回指令(例如 call/ret),中断指令(例
如 int)。
基于内容的相关性,过程调⽤会在后⾯的8086汇编⼦程序进⾏详细介绍,⽽中断指令则会在中断相关部分进⾏展开。
总结
不同的跳转指令都有着跳转间隔的限制,如果超出了跳转指令所约定的范围,则编译器会在编译时发现并报错。
跳转指令,特别是⽆条件跳转指令需要慎⽤,⽆所顾及的使⽤跳转指令很容易使得程序的可读性降低,
变成⼀团剪不断,理还乱的⾯团。这在⾼级语⾔程序的开发中同样适⽤,⼈的⼤脑所能同时理解的内容是有限的,必须通过合理的抽象将复杂程序构建成有着良好结构的⿊箱⼦。⽽随意的全局变量和不必要的输⼊/输出则会破坏这种模块化的结构,使⼈费解,在迭代中逐步脱离开发⼈员的控制,变成⼀个吞噬时间的⽆底洞。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论