指令

MOV (Move):名不副实,毛病不少

MOV名不副实,MOV指令并不是移动数据,而是复制数据的,这一点一定要清楚。 MOV指令是最常用的指令,考试考察唯一的点就是MOV指令的错误,整理如下

PUSH/POP:对,我就是2,怎么着吧

(关于堆栈的概念参见+++) 进栈使用PUSH指令(也就是将数据压入到堆栈中,注意是压入,原来的数据并不会丢失),语法如下: PUSH <寄存器或其它存储器寻址方法> 举例子: PUSH AXAX寄存器的数据压入堆栈。注意:不能用AH/AL等8位寄存器PUSH WORD PTR[BX]BX寄存器中存入的地址对应的数据转为WORD类型压入堆栈(关于PTR的作用参看++)。 请注意,上面的两个例子中共同的特点就是PUSH对应的数据都是16位的(也就是2个字节/1个字)。这就是堆栈的特点:就是2!在压入堆栈时都是以2个字节的形式压入。因此,下面的写法是错误的: +++ 再给一个例子: PUSH [2000H] 这就是直接寻址,也就是说将地址为2000H的数据压入了堆栈。不对,这话说得不对,要记住堆栈永远是2的,所以,应当是将地址为2000H2001H的数据压入了堆栈,依然是遵循着高地址进高位,低地址进低位的原则。注意,这里是堆栈,堆栈就是一个摞着一堆书的书筐,先入后出。同时,堆栈也是特地划出的一部分内存(详细参看+++),因此在算地址的时候还是从上向下地址越来越大,只不过底部是封口的,因此到了某个地址就不能再大了。

再考虑一下,SP是指针,指示的是地址,而且水涨船高,指针会被上抬,而地址的标注从来都是从上到下越来越大,那么当堆栈内容压栈内容增加(或说堆栈扩展),SP的值应当是减小,且每次减少一定是2或2的倍数

原理完全相同,POP出栈指令就是PUSH的逆过程。

LEA:OFFSET和我亲兄弟

LEA指令的作用就是取某个数据段的地址,并将地址返回给寄存器。 LEA SI, DB 例如上面的语句就是将DB的地址放到SI寄存器中。寄存器可以使用+++,一般习惯上使用SI/DI寄存器(参看)。 这个指令也可以使用MOV来替代,这个时候需要在要取的地址的数据名前加OFFSET,例如: MOV SI, OFFSET DB 这两个指令的效果基本是相同的,如果严谨一些下面的说法: (考)++++

ADD/SUB:

INC/DEC:

INC(Increase)自增指令,和C语言中的++类似(i++的写法实际上是i=i+1的一种变体,也可以称为是一种语法糖),不过不会再有什么先加后运算还是先运算后加的问题。INC指令只有一个操作数,如下: INC AX 这个语句的作用就是让AX自增1个值。当然如果你想让它增加2两个值就再写两句这样的指令:

INC AX      ; AX+1
INC AX      ; AX在AX+1的基础上再+1

DEC(Decrease)自减指令,和上述的自增指令的道理是一样,这里不再叙述。 对于INC/DEC指令,考试中不会出专门的题目来考察,但是自增/减指令是在程序设计中必须使用到的。比如依次读取某个地址的若干位字符串数据,我们可以通过LEA指令获得字符串的首地址,但是要依次读下来需要让地址依次增加。因此,INC/DEC指令除了在循环语句中做计数器(参看++++)外还常与处理字符串的程序联系紧密。在处理字符串的问题中,我们常用SI/DI寄存器作为地址指针。来看一个不完整的例子:

        LEA SI, STR1
        LEA DI, STR2
AGAIN:  MOV AL,  [SI]
        MOV [DI],AL
        INC SI
        INC DI
LOOP    AGAIN

依次分析一下每一句:

  1. STR1的首地址给SI
  2. STR2的首地址给DI
  3. SI寄存器里存储地址的数据放入AL中。看代码,注意这里的SI是加了方括号的,还记得这是什么寻址方式么?寄存器间接寻址。还记得那个例子么?储物间没有这个工件,需要问一个叫做SI的老师傅,师傅告诉我地址是多少多少,我再去仓库中按地址拿取工件。(详细参看++++)也就是说这里是取的SI寄存器存的地址所对应的在内存中的数据,放到AL中。注意,是AL,因为一个地址对应取到的是一个字节的内容,所以应该放到高位或低位中,而不是放到完整的16位寄存器。
  4. 再将AL寄存器数据放入DL寄存器对应地址的内存中。实际上,3、4两步合在是一个操作,即将[SI]的数据放到[DI]中,只不过MOV语句办不到直接操作内存的数,所以需要用寄存器中转(详细参看+++)。这里的所用的中转寄存器一般习惯上用AX
  5. 第一个字节的数据放完了,那么就让地址自增,以达到移动下一个字节的数据的目的。因此INC SI
  6. INC DI。思考一下,如果写成INC [DI]会有什么样的问题?
  7. 循环。

这里有一个需要注意的问题:自增/减指令在用于处理地址的时候需要看清楚题目的要求,比如,这个问题处理的数据是Byte(字节)型,一个地址就对应一个字节,这时候用自增/减使地址增减1是对的。但是如果处理的数据是Word(字)型或是Double(双字)型时就要注意,这时候分别要2个地址和4个地址对应一个字和一个双字,那么这个时候自增就不应当是1了。关于具体的案例和方法,参看:

注意不要有一个局限的思维,认为INC/DEC的指令只是针对非零的数字的。如果不用立即数,只通过指令得到1-1都是可以的。比如:

XOR AX, AX      ;AX寄存器内容清零
INC AX          ;使AX自增,即由0到1
XOR AX, AX      ;AX寄存器内容清零
DEC AX          ;使AX自减,即由0到-1

CMP (Compare):配合JXX食用更佳 (羞

CMP指令本质上和SUB指令是相同,区别就在于CMP指令不会影响操作数的值,只会影响FLAGS标志位CMP指令常见的应用就是和JXX指令构成条件结构语句,由CMP指定条件,对比两个值的大小,再由JXX判断标志位的值并在满足条件后跳转到指定段,不满足条件程序顺序执行。下面详细分析一下可能的情况: 因为CMP做的就是一个减法,所以求得的结果不外乎三种情况:零、负数、正数;转换一下思路归纳为两种情况:零、非零;非零有两种情况:负数、非负。那么来看影响的FLAGS标志位: ZF是零标志位,当结果为零的时候ZF=1JXX有两种写法,一种是JZ,判断零标志位是否为1,也就是结果是否为零;另一种是JNZ,判断零标志为是否不为1,即为0,也就是结果是否不为零。 判断ZF=0后(也就是结果为非零)接着判断是正还是负 SF是符号标志位,当结果为负,也就是符号为1的时候SF=1。同样的,判断的写法也有两种,JS/JNS,和上面的情况一样。

掌握了这个用法后基本上关于CMP指令的用法也就都了解了。

AND:俺想屏蔽谁就屏蔽谁

OR:你是我的1

XOR:愚蠢的寄存器们,滚回到0吧

XOR指令的作用就是用来求异或的逻辑运算。异或简单来说就是看你们两个是不是不一样,如果确实是不一样,那异或的结果为1;如果一样了,那异或的结果就是0。和其它的逻辑运算指令一样,运算最后的结果放在第一个操作数中。 XOR指令最常见的作用就是用于清零。比如 XOR AX, AX 这个语句就是让AX和自己比一比是不是一样啊?那废话肯定是一样的,那么逻辑运算结果为0,放入到第一个操作数AX中,所以达到了AX清零的目的。更重要的是,采用这样的清零方法可以FLAGS标志位的各个值也都清零

SHL/SHR:说,你们要乘除几个2?

在学习移位指令时需要对移位的流程和一些细节了解清楚。移位决不那么简单,而是有许多的用途。比如这里逻辑左移右移就可以实现乘除法,尽管8086有MUL/DIV指令完成乘除,但是其性能远不如用移位来实现。因为考试中单独考察MUL/DIV指令的题目几乎没有,且相对简单,所以本书不再介绍MUL/DIV指令。 需要注意的是,但凡涉及逻辑的问题,都是和0/1有关的,这里也不例外,所有的移位指令都是对于二进制数字而言。所以熟练地掌握十六进制到二进制的转换是关键(参看++++); SHL (SHift Left) 逻辑左移。先来看一下语法: 当移动1位时: SHL <目的操作数>, 1 当移动不止1位时(必须用CL寄存器中转):

    MOV CL, <移动次数/立即数>
    SHL <目的操作数>, CL

在之前已经接触过通过位移来实现乘法的例子(参看++++)。所不同的是,那个例子是乘以16以及16的倍数,但是对于普通的乘法来说,这样是没有意义的,要推而广之,可以乘以的数字更小。不难想象,那个例子是16进制的,因此向左移动移位就是乘以了16;类似的,在我们熟悉的十进制中,如果要对123乘以10应当如何做呢?当然是左移移一位(也就是补一个0),变成了1230;继续推广,对于二进制来说,如果向左移一位呢?当然是乘以2了,那么我如果要乘以8,那么就移动3位,乘以2^n,就是移动n位。当然如果是乘以奇数怎么办?比如乘以5,那么乘法本质上就是加法,乘以5可以转换为乘以4再加一遍原来的数。掌握这个道理了,下面来看这个例子:

SHR (SHift Right) 逻辑右移

RCL/RCR:

ROL/ROR:来吧,换个座位吧

JXX:给我立Flags,你说我往哪跳?

LOOP:我听CX爸爸的,DEC什么的滚蛋

LOOP指令的作用和C语言中的for语句是类似的,只不过LOOP指令指定了计数器、循环条件和步进(详细的和for语句的对比参看+++)。 考试中不会对LOOP有什么单独的考点,但是循环结构是程序设计中非常基础的一个结构,只有理解循环结构才能看懂程序。 语法结构很简单 LOOP <要循环的程序段> 需要注意的是

  1. 因为汇编语言是很纯粹的顺序执行语言,所以LOOP指令是要放在循环语句段结束的地方的。如果满足循环条件(即CX不为零)回到语句段起始位置继续顺序执行,如果不满足循环条件(即CX为零)那么顺序执行下面的语句,也就是跳出了循环。
  2. 另外,LOOP指令中CX寄存器的内容在执行一次循环后会自动减一的,所以除非是有必要的需求,循环语句段中不要再写DEC CX语句的。
  3. 这里需要特别提示一个在C语言中容易考到的问题——代码段循环的次数。汇编语言更加简单和基础,只需要记住CX寄存器中的数字是多少,那么代码就循环多少次。 接下来,我们换一个角度来看,LOOP指令就是一种特殊的JC???指令,特殊就特殊在它会自动将CX减一,而JC <要循环的程序段>是完全可以替代这样的功能的。因此,下面使用两种方法表示循环指令。 ++++DEC CX JNZ LOP

JXX:

TYPE:

PTR:

INT:中断

CLX:清空Flags

DB/DW/DD 定义字符

results matching ""

    No results matching ""