1、原码、反码、补码知识的复习:
三者的最高位均为符号位。我以前一直没弄明白的是为何8位补码的表示范围是-128~127,今天查阅了相关资料,于此记下。
仍然以8位为例:
原码的表示范围:-127~-0,+0~+127,共256个数字。正0的原码是0000 0000,负0的原码是:1000 0000,有正0负0之分,不符合人的习惯,待解决。
反码:除符号位,原码其余位取反而得。+0:0000 0000,-0:1111 1111仍然有正0负0之分。
补码:在反码的基础上加1而得。对原码的两种0同时末位加1。+0:0000 0000,-0:0000 0000(因为溢出导致8位全0)。消除了正0负0之别,如此一来,便节省出一个数值表示方式1000 0000,不能浪费,用来表示-128。-128特殊之处在于没有相应的反码原码。也可以这样考虑:
-1: 1111 1111
-2: 1111 1110(在-1的基础上减1,直接将补码减1即可)
-3: 1111 1101(在-2补码基础上减1,以下类似)
-4: 1111 1100
……
-127:1000 0001
-128:1000 0000
如此以来:8位补码表示范围是-128~+127因为0只有一种形式所以,仍然是256个数,若8位代表无符号数,则表示范围是:0~255,这就是为什么高级语言讲到数据类型,比如C++中的short类型时(16位长)说其表示范围是:-32768~+32767,而unsigned short表示的范围则是:0~65535
2、关于无符号数和有符号数:
无符号数及有符号数的定义就不多说了,任何计算机基础书籍都会讲到。
二者的区别:我们知道,有符号数在计算机中以补码的形式存储,无符号数其实就是正数,三码一致,存储形式即是其十进制真值对应的二进制数。所以可以这样说,无论有符号数还是无符号数,都是以补码(相对真值来说)的形式来存储的,补码在运算是符号位也会参与。
其实对计算机来说,它根本没有所谓的无符号有符号这样的约定机制,无符号有符号只不过是我们(程序员、学习者)看待二进制数据的方式,比如,对于16位的寄存器(比如ax)有符号数-1的存储形式是0FFFFH(即16个1,-1的补码,最高位符号位),而同时无符号数65535的存储形式也是0FFFFH。所以对计算机来说,它仅仅是存储了一串二进制,至于是有符号数还是无符号,你程序员要心中有数。C++代码举例:
short a=-1;unsigned short b=a;cout<<
3、汇编相关
已经说过,数据以补码形式存储于计算机中(有符号、无符号),自然,寄存器,比如ax,存的也是补码,比如:mov ax,-96,查看便知ax存储的是0FA0H。在高级语言编程中,我们完全不用考虑什么原码补码。汇编的add指令在运算时,比如
mov al,0
add al,-1
它不去“考虑”什么有符号无符号,反正数据都以补码(无符号数据便是正数,补码不变)的形式存储、运算,它只管将al中的补码与99的补码加一起然后存入al中(补码的运算特点是:符号位也参与运算),我们知道运算后al存储的是1111 1111(0FFH),至于你如何看待这个结果,由你程序员控制,你看成有符号数它便是-1,看成无符号数它便是255。
与此同时,相关的寄存器也会有相应的机制,来配合编程人员。比如标志寄存器中的SF标志位,如果编程人员将数据看成无符号数,则我们SF对我们是毫无意义的——尽管对我们毫无意义,但计算机依然以“有符号数”的态度来“看待”参与运算和存储的数据,比如
mov ax,12
add ax,-18
结果为负,sf=1(结果如果为非负,则sf=0)
计算机不管编程人员以什么眼光(有符号无符号)来看待数据,反正它把所有的可能都考虑到了,把相应的工作也做了,编程人员可按需而来。不妨想象一种情景:假如计算机完全以无符号数来看待数据,压根没有sf标志位,那么如果程序员想知道某数据的正负(以有符号数来看待数据)时,便无从下手了。
可以说,计算机仁至义尽,剩下的便是程序员的事了。
4、汇编中的CF、OF、SF标志位及cmp操作
CF对无符号数运算是有意义的,OF,SF对有符号数而言是有意义的。ZF则都适用。
一个是进位借位标志位一个是溢出标志位,溢出是指:在进行有符号数运算时,结果超过了机器所有表示的范围(五爽《汇编语言》),可知,OF只对有符数运算有意义,无符号数不关心这种溢出。无符号数关心的是最高位向更高位借位进位问题。(从这个层面上来讲,其实也可将这种向更高位借位进位看成无符号数的“溢出”)。所以CF只对无符号数的运算有意义,即当考虑CF标志位的时候要做到心中有数:现在参与运算的都是无符号数——这依然是程序员的事。
可能会有这样的疑问:两个数运算,如果溢出了,那么以“无符号的眼光”看来,不也是产生了借位或者进位吗?或者反之,两个数运算,如果进位或者借位了,那么以”有符号的眼光“看来,不也是产生了溢出吗?
对于第一个问题:不一定,比如0111 1111+0000 0001,看成有符号数运算的话,结果明显溢出,但如果看作无符号的运算的话并没有向更高位(无符号数无符号位,均为数值位)进位。
又如:mov al,98 add al,99(即0110 0010+0110 0011)运算后CF=0,OF=1。具体的过程可以这样考虑:
首先,用的是al来运算,所以计算机知道做的是8位运算,加得结果:1100 0101,对于无符号来说无进位,CF=0,对于有符号来说溢出了:OF=1.
对于第二个问题:也不一定。如:
mov al,0F0H
add al,88H执行后CF=1,OF=1,但是,请看另一例:
mov al,0F0H
add al,78H
对于0F0H和78H,首先要明白,如果看作无符号数,则便是240+120,明显会在高位向更高位产生进位,所以CF=1,如果看作有符号数,作为人类的程序员,有两种方式可以判定是否溢出:
a、将0F0H和78H(补码)转换成10进制数值(按照符号位不变其余位取反然后末位加1转成原码,再求),相加看是否在-128~127之内。也可按补码求真值的公式直接求出补码对应的真值:真值x=-2^n*xn+其余各位2进制对应真值。其中xn为最高位即符号位,为0或者1。
b、单符号判溢出方法:这是组成原理(白中英主编第4版)介绍的判断溢出的方法,补码相加,看符号位及最高有效位。将16进制的补码转换成2进制:1111 0000和0111 1000,相加:
1111 0000
0111 1000
可看出最高有效位有符号位均产生了进位,这种情况不视为溢出(只有二者之一产生进位时才算溢出)。
有符号数的溢出问题引发了cmp操作的相关问题。
cmp是比较指令,相当于减法,但不会将结果保存在目的操作数中,但会影响标志位sf,zf,cf。利用它既可以对无符号数比较也可以对有符号数进行比较。至于是有符号数还是无符号数,这依然取决于我们的看待方式,如:
mov ax,8
mov bx,3
cmp ax,bx
看成无符号数分析:因为无须借位,cf=0(据此可以判断ax>bx),因为结果不为0,zf=0(据此可以判断ax bx是否相等)
对于有符号数,情况要复杂一些,因为牵扯到了溢出问题。
比如已知(ah)=22H,(bh)=0A0H,二者都是补码,一个为正一个为负,二者对应的真值分别为:34和-96,逻辑上讲(ah)>(bh),但cmp ah,bh运算时会溢出:34-(-96)=-230,小于-128。另外,我们可以直接用16进制减法手动计算结果:
22
A0
---------
82H,即1000 0010,知结果为-1*2^7+2=-126,这显然是个错误的结果。计算机不会进行减法,它得转成加法。用补码加法计算有个好处,可用上边已经介绍过的单符号位判溢出方法判断结果是否溢出。过程:x补=22H=0010 0010,y补=1010 0000,求x补-y补,这是补码减法,转换成加法:x补-y补=x补+[-y]补,由y补求[-y]补:包括符号位全部取反,然后末位加1(参照白中英组成原理课本相关内容),转变后即为:0010 0010+0110 0000,由单符号位判溢出方法可知,只有高位有效位产生了进位,进给了符号位,这是一个溢出,一个上溢。
要注意的是:引入补码带来的便处之一使得负数的加法转成补码的加法(负数的加法便是减法),但是如果不加控制补码之间也会进行减法,此时也要将相应的减法转成加法运算,当然这种转换的工作不是我们所做的,转换的原理如上所述。
因为溢出问题,导致上面cmp结果为负,sf=1,如果仅据此就说(ah)<(bh)明显是错误的,此时还要结合of位,因为有溢出,所以of=1(如果of等于0,说明没有溢出,则我们可以放心地使用sf来判断)。一条规律是:有溢出,则cmp结果与of表示的相反,本来打算验证一下,还是算了,抱着复习相关知识的心态,写了这么多,蛋疼。