汇编语言笔记(二)

来源:互联网 发布:java并发实例 编辑:程序博客网 时间:2024/05/16 00:58

一、80386机器提供了几种扩展数据大小的操作码
  首先要记住计算机永远都不知道一个数是无符号数还是有符号数。
  有符号数和无符号书的区别是通过程序员使用不同的操作来体现的。
    对于无符号数,只要把它的高位简单的置0即可。例如扩展AL中的一个
    byte值为一个字节大小的值从八位扩展到16位,用如下操作即可
       MOV    AH    0;
     然而我们并不能用MOV操作把AX中一个字的无符号数扩展为EAX中的两个字的无符号数。
那么为什么会这样呢?原因在于80386没有提供这种把EAX高16位置0的MOV操作。至于为什么没有提供?
出于什么样的考虑还需去查找资料。在这里就不得而知了。
  80386种解决的办法是提供了另一种操作即:MOVZX.这个操作必须提供两个参数:第一个参数作为目
  标寄存器,这个寄存器必须是16或者32位。第二个参数是源寄存器,该寄存器必须是8或者16位。或
  者第二个参数也可以是一个字节或一个字的内存对象。总之目标寄存器必须比源数据大。这个是可以
  理解的,如果必须小那就难以理解了。
 
     下面是几个例子:
      movzx eax, ax ; 扩展ax 到 eax
      movzx eax, al ; 扩展al 到eax
      movzx ax, al ; 扩展al 到 ax
      movzx ebx, ax ; 扩展ax 到 ebx
 
     对于有符号数,就没有这么简单的MOV操作了。但是8086提供了几种操作用来扩展有符号数。不管用什么方法,
 不解决是不行的。
      CWT 用于把一个字节有符号数扩展为一个字有符号数
        这个操作把AL扩展为AX
      CWD 用于把一个字的有符号数扩展为两个字的有符号数
      这个操作可以把AX 扩展到 DX:AX.注意这里DX:AX看作一个32位的寄存器。虽然他们是由两个寄存器组成。高16位存于DX中而低16位存于AX中。
 
      CWDE 用于把一个字的有符号数扩展为两个字的有符号数
       这个操作可以把AX 扩展到 EXA
      CDQ 用于把两个字的有符号数扩展为四个字的有符号数
       这个操作可以把EXA扩展到 EDX:EXA中
 
      最后MOVSX操作和movzx 操作规则是一样的只不过它用于有符号数。

二、有符号数无符号数的C语言表示
      在C语言中我们也会遇到有符号数无符号数的扩展问题。c语言中的变量可以被声明为有符号数或者无符号数。
(int 是有符号数)
      看一下一下几行代码:
      1 unsigned char uchar = 0xFF;
   2 signed char schar = 0xFF;
   3 int a = (int ) uchar ; / a = 255 (0x000000FF) /
   4 int b = (int ) schar ; / b = -1 (0xFFFFFFFF) /
   
   第三行中是一个无符号数的扩展,用到了MOVZX操作。第四行中是一个有符号数的扩展用到了MOVSX操作
   
   char ch;
   while( (ch = fgetc(fp)) != EOF ) {
    / do something with ch /
   }
   关于这方面的内容,有一个c语言的BUG.考虑上述的代码。是fgetc()函数的原形
     
     int fgetc( FILE * );
     
       问题是:这个函数是读取一个char的,而为什么要返回一个int呢?原因在于通常情况下这个函数是返回
   一个char(用高位置0的方法扩展为一个int),但是这里有一个值却不是一个character EOF 是一个宏定义通常的值为-1.
   因此fgetc返回一个char扩展为一个int表示为十六进制的形式因该为:000000xx形式。或者是-1扩展为一个十六
   进制的形式为FFFFFFFF
   
       代码中问题存在于他返回的是一个int值却存储在一个char里。这样在c语言里为自动的去掉高位把它变为一个char
   值。问题只在有范围值为000000FF 和 FFFFFFFF 时。它们都会变为FF的byte值。这样在这个循环中就没有办法区分000000FF
   和 FFFFFFFF的不同
     
     确切的说上述代码究竟怎么运行取决于char是有符号还是无符号.为什么呢?因为在第二行中,ch是和一个int值进行
   比较的。ch将扩展为一个int.
       如果ch是一个无符号数,那么FF将扩展为000000FF.这个循环将永远循环下去。
       如果ch是个有符号数,那么FF将扩展为FFFFFFFF,循环可以停止,但现在却没有办法区分从文件中读出来的000000FF还是结束符
       FFFFFFFF
      
       解决的方法是把字符定义为一个int,那么就不存在转化的问题了。

三、Two’s complement arithmetic

   正像先前看到的,add操作码执行的是加的操作,sub操作码执行的是减的操作。在这两个操作中
会用到两个flag寄存器,一个是溢出标示(overflow flag),一个是进位标示(carry flag)。
      在有符号算法中,当结果太大超出目标范围的时候,overflow flag会被设置为true。而carry flag
在加操作的进位和减操作的借位运算时将会被用到。所以carry flag可以用来监测无符号数是否溢出。
对于Two’s complement算法,add和sub操作对于无符号数和有符号数规则是相同的,因此它们可以用于
有符号数和无符号数:
   002C      44
  + FFFF    + (-1)
  -------   ------
   002B      43
      上面的例子是两个有符号数的加操作.产生了一个进位.但这个进位是丢丢到的。
     
     
      但是这里却有两个乘和两个除操作。两个乘操作是:MUL or IMUL。MUL是用来进行两个无符号数
的乘操作。而IMUL是用来进行两个有符号数的乘操作。为什么会需要两个操作呢?那是因为对于2’s complement
算法,有符号数和无符号数的乘法规则是不同的。为什么会这样呢?考虑一下以下的乘法:FF * FF,用无符号数
的乘法结果应该是255 * 255 = 65025(FE01 十六进制)。如果FF是个有符号数那么结果为:-1 * -1 = 1(0001 十六进制);

      对于乘法的操作码,有很多种形式:
      mul source
      这个是最古老的一种形式,source可以是一个寄存器,也可一个是一个内存的指针。但确不能是
立即数。确切的讲mul执行什么样的操作依赖于source操作数的大小。如果操作数是一个字节大小,那么操作数
就会乘上AL中的数值并把结果保存在AX中。如果source是十六位的那么操作数就会乘上AX中的值并把结果
保存到DX:AX中。如果source是32位的那么操作数会乘上EAX中的数值并把结果保存到EDX:ECX中。

      imul操作码和mul有着相同的形式,但是还有其它两种形式:
      imul dest, source1
   imul dest, source1, source2
   -------------------------------------------------------------                                           |
   dest             source1             source2              Action                                    |
                         reg/mem8                                     AX = AL*source1                 |
                         reg/mem16                                   DX:AX = AX*source1          |
                         reg/mem32                                   EDX:EAX = EAX*source1  |
   reg16           reg/mem16                                   dest *= source1       |
   reg32           reg/mem32                                   dest *= source1       |
   reg16           immed8                                         dest *= immed8        |
   reg32           immed8                                         dest *= immed8        |
   reg16           immed16                                       dest *= immed16       |
   reg32           immed32                                       dest *= immed32       |
   reg16           reg/mem16      immed8              dest = source1*source2|
   reg32           reg/mem32      immed8              dest = source1*source2|
   reg16           reg/mem16      immed16            dest = source1*source2|
   reg32           reg/mem32      immed32            dest = source1*source2|
   ---------------------------------------------------------------
   上图列出了可能的组合;
   
   相应的也有两种除法操作:DIV和IDIV.它们分别作无符号数和有符号数的除法操作。我们来看一下
   
   DIV source
   
   1.source是8位的,那么AX除以source中的值。除数被放在AL寄存器中.剩余数被放在AH中:例如
    7 / 3 那么AL的值为2,AH的值为1。
   2.source是16位的:那么DX:AX中的值除以source,结果是除数被放在AX中,余数被放在AX中
   3.source是32位的: 那么EDX:EAX中的值除以source.除数放在EAX中,余数放在EDX中
   
   
       IDIV操作码和DIV是相同的,它不同于IMUL有那么多的形式。如果除数太大或者被除数为0那么程序就会
   报错终止。一个常见的错误是在进行除法前忘记初始化DX或者EDX的值。
       NEG操作码是用Two’s complement算法计算符号数的否定操作。它的操作数可以为8,16,32位的寄存器
   或者内存数