n 8086CPU的flag寄存器的结构:
n flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具有特殊的含义
1.ZF标志
n flag的第6位是ZF,零标志位。
它记录相关指令执行后,
n 结果为0 ,ZF = 1
n 结果不为0,ZF = 0
n 例子:比如:
mov ax,1
sub ax,1
指令执行后,结果为0,则ZF = 1。
mov ax,2
sub ax,1
指令执行后,结果为1,则ZF = 0。
n 对于ZF的值,我们可以这样来看,ZF标记相关指令的计算结果是否为0,如果为0,则在ZF要记录下“是0”这样的肯定信息。
n 在计算机中1 表示逻辑真,表示肯定,所以当结果为0的时候 ZF=1,表示“结果是0 ”。如果结果不为0,则ZF要记录下“不是0”这样的否定信息。
n 在计算机中0表示逻辑假,表示否定,所以当结果不为0 的时候ZF=0,表示“结果不是0”。
2.PF标志
n flag的第2位是PF,奇偶标志位。
它记录指令执行后,结果的所有二进制位中1的个数:
n 为偶数,PF = 1;
n 为奇数,PF = 0。
n 示例
n 指令:mov al,1
add al,10
执行后,结果为00001011B,其中有3(奇数)个1,则PF=0;
n 指令:mov al,1
or al,10
执行后,结果为00000011B,其中有2(偶数)个1,则PF=1;
3.SF标志
n flag的第7位是SF,符号标志位。
它记录指令执行后,
n 结果为负,SF = 1;
n 结果为正,SF = 0。
n 有符号数与补码
n 示例
mov al,10000001B
add al,1
结果: (al)=10000010B
解释:
n 我们知道计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。
n 比如:
n 00000001B ,可以看作为无符号数 1 ,或有符号数+1;
n 10000001B ,可以看作为无符号数129,也可以看作有符号数-127。
为什么有符号的数就是-`127呢?
解释:
1). 将该数的所有位取反(invert),也就是将所有的零改为一,所有的一改为零。
2). 将取反后的数加一(忽略溢出)。
例如,以下就是计算十进制数-5的8位表示的步骤:
%0000_0101 |
五 (二进制) |
%1111_1010 |
所有位取反 |
%1111_1011 |
加一,得到−5 (二进制补码表示) |
现在对-5再取负,结果就是5(%0000_0101),正如我们所预期的一样:
%1111_1011 |
-5的二进制补码表示 |
%0000_0100 |
所有位取反 |
%0000_0101 |
加一,得到5 (二进制) |
-59的补码表示为: |
|
结合上面的例子:
10000001B 如果先声明了为有符号数,那么第一位就是代表符号,此处为1,表示负数,然后将此此数取反+1,01111111B=64+32+16+8+4+2+1=127,也就是-127。
3). 理解有符号数和无符号数
我们所讲的数都是正数。同样是年纪和工资,前者不需要有负值,但后者可能需要——至少所有的老板都这样认为。
那么,负数在计算机中如何表示呢?
这一点,你可能听过两种不同的回答。
一种是教科书,它会告诉你:计算机用“补码”表示负数。可是有关“补码”的概念一说就得一节课,这一些我们需要在第6章中用一章的篇幅讲2进制的一切。再者,用“补码”表示负数,其实一种公式,公式的作用在于告诉你,想得问题的答案,应该如何计算。却并没有告诉你为什么用这个公式就可以和答案?
另一种是一些程序员告诉你的:用二进制数的最高位表示符号,最高位是0,表示正数,最高位是1,表示负数。这种说法本身没错,可是如果没有下文,那么它就是错的。至少它不能解释,为什么字符类型的-1用二进制表示是“1111 1111”(16进制为FF);而不是我们更能理解的“1000 0001”。(为什么说后者更好理解呢?因为既然说最高位是1时表示负数,那1000 0001不是正好是-1吗?)。
让我们从头说起。
1、你自已决定是否需要有正负。
就像我们必须决定某个量使用整数还是实数,使用多大的范围数一样,我们必须自已决定某个量是否需要正负。如果这个量不会有负值,那么我们可以定它为带正负的类型。
在计算机中,可以区分正负的类型,称为有符类型,无正负的类型(只有正值),称为无符类型。
数值类型分为整型或实型,其中整型又分为无符类型或有符类型,而实型则只有符类型。
字符类型也分为有符和无符类型。
比如有两个量,年龄和库存,我们可以定前者为无符的字符类型,后者定为有符的整数类型。
2、使用二制数中的最高位表示正负。
首先得知道最高位是哪一位?1个字节的类型,如字符类型,最高位是第7位,2个字节的数,最高位是第15位,4个字节的数,最高位是第31位。不同长度的数值类型,其最高位也就不同,但总是最左边的那位(如下示意)。字符类型固定是1个字节,所以最高位总是第7位。
(红色为最高位)
单字节数: 1111 1111
双字节数: 1111 1111 1111 1111
四字节数: 1111 1111 1111 1111 1111 1111 1111 1111
当我们指定一个数量是无符号类型时,那么其最高位的1或0,和其它位一样,用来表示该数的大小。
当我们指定一个数量是无符号类型时,此时,最高数称为“符号位”。为1时,表示该数为负值,为0时表示为正值。
3、无符号数和有符号数的范围区别。
无符号数中,所有的位都用于直接表示该值的大小。有符号数中最高位用于表示正负,所以,当为正值时,该数的最大值就会变小。我们举一个字节的数值对比:
无符号数: 1111 1111 值:255 1* 27 + 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20
有符号数: 0111 1111 值:127 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20
同样是一个字节,无符号数的最大值是255,而有符号数的最大值是127。原因是有符号数中的最高位被挪去表示符号了。并且,我们知道,最高位的权值也是最高的(对于1字节数来说是2的7次方=128),所以仅仅少于一位,最大值一下子减半。
不过,有符号数的长处是它可以表示负数。因此,虽然它的在最大值缩水了,却在负值的方向出现了伸展。我们仍一个字节的数值对比:
无符号数: 0 ----------------- 255
有符号数: -128 --------- 0 ---------- 127
同样是一个字节,无符号的最小值是 0 ,而有符号数的最小值是-128。所以二者能表达的不同的数值的个数都一样是256个。只不过前者表达的是0到255这256个数,后者表达的是-128到+127这256个数。
一个有符号的数据类型的最小值是如何计算出来的呢?
有符号的数据类型的最大值的计算方法完全和无符号一样,只不过它少了一个最高位(见第3点)。但在负值范围内,数值的计算方法不能直接使用1* 26 + 1* 25 的公式进行转换。在计算机中,负数除为最高位为1以外,还采用补码形式进行表达。所以在计算其值前,需要对补码进行还原。这些内容我们将在第六章中的二进制知识中统一学习。
这里,先直观地看一眼补码的形式:
以我们原有的数学经验,在10进制中:1 表示正1,而加上负号:-1 表示和1相对的负值。
那么,我们会很容易认为在2进制中(1个字节): 0000 0001 表示正1,则高位为1后:1000 0001应该表示-1。
然而,事实上计算机中的规定有些相反,请看下表:
二进制值(1字节) |
十进制值 |
1000 0000 |
-128 |
1000 0001 |
-127 |
1000 0010 |
-126 |
1000 0011 |
-125 |
... |
... |
1111 1110 |
-2 |
1111 1111 |
-1 |
首先我们看到,从-1到-128,其二进制的最高位都是1(表中标为红色),正如我们前面的学。
然后我们有些奇怪地发现,1000 0000 并没有拿来表示 -0;而1000 0001也不是拿来直观地表示-1。事实上,-1 用1111 1111来表示。
怎么理解这个问题呢?先得问一句是-1大还是-128大?
当然是 -1 大。-1是最大的负整数。以此对应,计算机中无论是字符类型,或者是整数类型,也无论这个整数是几个字节。它都用全1来表示 -1。比如一个字节的数值中:1111 1111表示-1,那么,1111 1111 - 1 是什么呢?和现实中的计算结果完全一致。1111 1111 - 1 = 1111 1110,而1111 1110就是-2。这样一直减下去,当减到只剩最高位用于表示符号的1以外,其它低位全为0时,就是最小的负值了,在一字节中,最小的负值是1000 0000,也就是-128。
4).汇编中的确良有符号数和无符号数探讨
这个问题,要是简单的理解,是很容易的,不过要是考虑的深了,还真有些东西呢。
下面我就把这个东西尽量的扩展一点,深入一点和大家说说。
只有一个标准!
在汇编语言层面,声明变量的时候,没有 signed 和 unsignde 之分,汇编器统统,将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只有这一个标准!汇编器不会区分有符号还是无符号然后用两个标准来处理,它统统当作有符号的!并且统统汇编成补码!也就是说,db -20 汇编后为:EC ,而 db 236 汇编后也为 EC 。这里有一个小问题,思考深入的朋友会发现,db 是分配一个字节,那么一个字节能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围,怎么可以?是的,+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当然更多的字节更好了)是可以装下的,应为:00 EC,也就是说 +236的补码应该是00 EC,一个字节装不下,但是,别忘了“截断”这个概念,就是说最后汇编的结果被截断了,00 EC 是两个字节,被截断成 EC ,所以,这是个“美丽的错误”,为什么这么说?因为,当你把 236 当作无符号数时,它汇编后的结果正好也是 EC ,这下皆大欢喜了,虽然汇编器只用一个标准来处理,但是借用了“截断”这个美丽的错误后,得到的结果是符合两个标准的!也就是说,给你一个字节,你想输入有符号的数,比如 -20 那么汇编后的结果是符合有符号数的;如果你输入 236 那么你肯定当作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果是符合无符号数的。于是给大家一个错觉:汇编器有两套标准,会区分有符号和无符号,然后分别汇编。其实,你们被骗了。:-)
存在两套指令!
第一点说明汇编器只用一个方法把整数字面量汇编成真正的机器数。但并不是说计算机不区分有符号数和无符号数,相反,计算机对有符号和无符号数区分的十分清晰,因为计算机进行某些同样功能的处理时有两套指令作为后备,这就是分别为有符号和无符号数准备的。但是,这里要强调一点,一个数到底是有符号数还是无符号数,计算机并不知道,这是由你来决定的,当你认为你要处理的数是有符号的,那么你就用那一套处理有符号数的指令,当你认为你要处理的数是无符号的,那就用处理无符号数的那一套指令。加减法只有一套指令,因为这一套指令同时适用于有符号和无符号。下面这些指令:mul div movzx … 是处理无符号数的,而这些:imul idiv movsx … 是处理有符号的。
举例来说:
内存里有 一个字节x 为:0x EC ,一个字节 y 为:0x 02 。当把x,y当作有符号数来看时,x = -20 ,y = +2 。当作无符号数看时,x = 236 ,y = 2 。下面进行加运算,用 add 指令,得到的结果为:0x EE ,那么这个 0x EE 当作有符号数就是:-18 ,无符号数就是 238 。所以,add 一个指令可以适用有符号和无符号两种情况。(呵呵,其实为什么要补码啊,就是为了这个呗,:-))
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 。无符号的情况下用 mul ,得到:0x 01 D8 就是 472 。(
n 如果在进行有符号数运算时发生溢出,那么运算的结果将不正确。
n 就上面的两个例子来说:
mov al,98
add al,99
n 197 1100 0101=128+64+4+1=197
n 32+16+8+2+1=
n 7 6 5 4 3 2 1 0
n 0 0 1 1 1 0 1 1
n 1 1 0 0 0 1 0 1
n
n
n add指令运算的结果是(al)=0C5H ,因为进行的是有符号数运算,所以 al中存储的是有符号数,而0C5H是有符号数-59的补码
1. Je的用法:
MOV AX,DATAS
MOV DS,AX
mov ax,1
mov bx,1
cmp ax,bx
je print
2. 为举例方便说一下JNZ和JZ
测试条件
JZ ZF=1
JNZ ZF=0
即JZ=JUMP IF ZERO (结果为0则设置ZF零标志为1,跳转)
JNZ=JUMP IF NOT ZERO
3. TEST属于逻辑运算指令
功能: 执行BIT与BIT之间的逻辑运算
测试(两操作数作与运算,仅修改标志位,不回送结果).
TEST对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。TEST AX,BX 与 AND AX,BX 命令有相同效果
语法: TEST R/M,R/M/DATA
影响标志: C,O,P,Z,S(其中C与O两个标志会被设为0)
运用举例:
1.TEST用来测试一个位,例如寄存器:
TEST EAX, 100B; B后缀意为二进制
JNZ ******; 如果EAX右数第三个位为1,JNZ将会跳转
是这样想的,JNZ跳转的条件是ZF=0,ZF=0意味着ZF(零标志)没被置位,即逻辑与结果
2.TEST的一个非常普遍的用法是用来测试一方寄存器是否为空
TEST ECX, ECX
JZ SOMEWHERE
如果ECX为零,设置ZF零标志为1,JZ跳转
4.neg求补码
0000 0001
1111 11110+1=1111 1111
5.sbb
n sbb是带错位减法指令,它利用了CF位上记录的借位值。
n 格式:sbb 操作对象1,操作对象2
n 功能:
操作对象1=操作对象1–操作对象2–CF
n 比如:sbb ax,bx
实现功能: (ax) = (ax) – (bx) – CF
6.adc带进位加法指令
n adc是带进位加法指令 ,它利用了CF位上记录的进位值。
n 格式: adc 操作对象1,操作对象2
n 功能:
操作对象1=操作对象1+操作对象2+CF
n 比如:adc ax,bx 实现的功能是:
(ax)=(ax)+(bx)+CF
n adc指令示例(一)
n mov ax,2
mov bx,1
sub bx,ax
adc ax,l
执行后,(ax)=4。
adc执行时,相当于计算: (ax)+1+CF=2+1+1=4。
n adc指令示例(三)
n mov al,98H
add al,al
adc al,3
执行后,(ax)=34H。
adc执行时,相当于计算: (ax)+3+CF=30H+3+1=34H。
n 可以看出,adc指令比add指令多加了一个CF位的值。
为什么要加上CF的值呢?
CPU为什么要提供这样一条指令呢?
n 在执行 adc 指令的时候加上的 CF 的值的含义,由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。
n 显然,如果CF 的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。
7.cmp指令
n cmp 是比较指令,功能相当于减法指令,只是不保存结果。
n cmp 指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果
n cmp指令
n 格式:cmp 操作对象1,操作对象2
n 功能:计算操作对象1–操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
n 比如:cmp ax,ax
做(ax)–(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
指令执行后:
ZF=1,
PF=1,
SF=0,
CF=0,
OF=0。
n 下面的指令:
mov ax,8
mov bx,3
cmp ax,bx
执行后: (ax)=8,
ZF=0,
PF=1,
SF=0,
CF=0,
OF=0。
n 其实,我们通过cmp 指令执行后,相关标志位的值就可以看出比较的结果。
n cmp ax,bx