• 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汇编子程序进行详细介绍,而中断指令则会在中断相关部分进行展开。

    总结  

      不同的跳转指令都有着跳转间隔的限制,如果超出了跳转指令所约定的范围,则编译器会在编译时发现并报错。

      跳转指令,特别是无条件跳转指令需要慎用,无所顾及的使用跳转指令很容易使得程序的可读性降低,变成一团剪不断,理还乱的面团。这在高级语言程序的开发中同样适用,人的大脑所能同时理解的内容是有限的,必须通过合理的抽象将复杂程序构建成有着良好结构的黑箱子。而随意的全局变量和不必要的输入/输出则会破坏这种模块化的结构,使人费解,在迭代中逐步脱离开发人员的控制,变成一个吞噬时间的无底洞。

  • 相关阅读:
    S3C2440的LCD虚拟显示测试
    arm-linux-gcc编译器测试
    韦东山教程ARM的时钟设置出现的问题及其解决方法
    程序在nor flash中真的可以运行吗?
    存储器的速度
    程序测试的方法
    对编程的一些思考

    [算法题] 字节流解析
    [C/C++]函数指针和函数分发表
  • 原文地址:https://www.cnblogs.com/xiaoxiongcanguan/p/12457486.html
Copyright © 2020-2023  润新知