高级条件if的底层原理

布尔表达式

在高级语言中 布尔值用来做条件判断从而对程序走向作出控制,其中涉及到的运算符

&&

对2个操作数作&&运算的结果依据是 只要其中一个数是false, 整个表达式的值就是false

||

对2个操作数作||运算的结果依据是 只要其中一个数是true, 整个表达式的值就是true

! (单目运算符 对操作数取反)

以上3个逻辑运算符是高级语言里定义的,操作对象是语言里的类型。在 汇编里的逻辑操作是针对于bit位 的,要实现高级语言里的效果必需配合特定的指令

注:下面出现的汇编语法都是GAS汇编器的语法格式,R代表寄存器,M代表.data里的内存变量,L代表立即数(0b二进制前缀 0x十六进制前缀)M和L后面跟上的数字代表操作数是多少位的(一般是8位 16位 32位 64位)


汇编里的逻辑运算指令

not(非)

指令格式:

not_S %R\M
高级语言里不同, 这个指令会对操作数的每一个bit位取反

.data
num1: .word 12
.text
.globl _main
_main:
notw num1 
notw %eax
.end

not实际上是对操作数取反码, 没有带符号和无符号之称, 并且会覆盖操作数
如果要对内存地址操作,必需先将内存地址复制到寄存器里, 对寄存器操作

.data
num1: .word 12
.text
.globl _main
_main:
leal num1, %eax
notw (%eax)
.end
...


and 指令

格式:

and_S $L%R\M, %R\M
这个指令是对 相应的bit位进行比较, 如果其中一个bit是0 那么两个bit位计算的结果就是0,和高级语言里性质是一样的,只是比较的对象及范围是不同的

.data
num1: .byte 0b10100101

.text
.globl _main
_main:
andb $0, num1 #结果num1的所有bit位都会置0


and最常用的是将某个数置0

  • 很多时候可以用来强制使某些bit位置0

比如 要对 num1的第6位置0, 那么根据and的操作特性,只要构建出一个数,第6位是0,其它bit位全是1,然后和num1作and操作就可以了, 这种构建出来的专门针对于某个bit位操作的数字一般称之为掩码, 比如这一次构建出来的掩码就是 0b11011111

  • and有时也常用来判断某一个数是不是偶数

在二进制里,偶数的特征比较明显,它的最后一位bit位一定是0, 基于这个特征, 我们需要做的就是判断这个数的最后一个bit位是不是0,那么可以直接将其与1 and,其他bit位全不用考虑, 那么构建出来的掩码无非是 最低是1,其他位全是0

  • and也可以快速将小写字母转换为大写字母

观察ascii, 英文字母大小写编码有一定的规律, 对应的小写字母比大写字母总是大32,从二进制位观察刚好是 第5个bit位同, 小写是1, 大写是0, 其余都相同, 所以构建出来的掩码是 0b11011111, 只要将这个掩码和小写做and 就可以将其转换成对应的大写字母

and 操作会覆盖掉目的操作数

or操作

格式:

or_S $L%R\M, %R\M
or的运算规则是 对两个作运算的bit位,只要其中一个是1,那么运算出来的结果就是1,也就意味着, 只有当两个都是0的时候,结果才是0

or的特性是将某个bit位强制置1,和and是相反的
orb $0b0001 0000, num1 #num1 = 0b1100 0010

前面用and可以将小写转换成大写, 利用or的特性,构建出相应的掩码可以强制将某个字母变成小写, 不管当前这个字母是大写还是小写
a = 0x61 = 0b0110 0001
A = 0x41 = 0b0100 0001
mask = 0x20 = 0b0010 0000

orb mask, a #0b0110 0001
orb mask, A #0b0110 0001

xor指令

格式:

xor $L%R\M, M%R

关于xor的介绍请参考探讨一下异或(xor)为什么能还原的问题

上面的4条件指令,都是操作bit位,执行指令后我们只是根据结果自己推导出条件是否成立


现在我们来考虑一个问题,在汇编里怎么判断一个数是否为0?

仔细想想这个问题, 目前为止,似乎并没有学过哪一条指令来判断一个数是否为0

CPU状态位ZF

在CPU中ZF位改变一般是被动的

  • ZF的改变是随着某些指令被执行后改变的

  • 很少主动用指令去改变ZF

  • 当ZF ==0的这一刻,那么cpu刚执行的指令的结果肯定不是0

  • 当ZF ==1的这一刻,那么cpu刚执行的指令的结果一定是0

所以在汇编里逻辑上去判断一个数是不是0, 并没有很直接的指令拿去用,意思就是说,目前为止并没有指令支持我们根据条件做我们想做的事情,但是我们可以先根据理论来构建出高级语言里的if else

通常判断一个数是否为0

方法1 做减法

在现实中一眼就能看出来,但追究问题的本质,通常判断一个数是不是为0,那么直接拿这个数减去0,看看结果是不是为0?这不明显是废话吗,一点意义没有!!但在计算机里还必须要做这一步实质的运算,具体来计算机对这个数做了一个减法,减数是0,被减数是要和0比较的数 转换成减法指令

subw $0, num1 #假设num1 = 12

这里要注意的是这条指令是谁减谁 num1 = num1 - 0
执行完这条指令后, 会发生这样的变化:
1、结果的值会被保存在 num1中
2、在sub的过程中,cpu内部会根据结果是否为0重置ZF位

现在给出为 伪代码

subw $0, num1  

zfgoto partend

addw $10, num1

partend:
...

上面的zfgoto 是我故意写出来的不存在的指令, 意思是 sub后,如果ZF==1(表示num1和0相等) 就goto到partend,不会做add操作

方法2 or 或 xor

xorw 0, num1
这个过程是和sub一样的

上面根据标志位zf可以判断出一个数和0的关系,那么就可以拓展出2个数的大小关系,给出一个高级语言的例子,假设都是16位的

num1 = 10, num2 = 7, num3 = 0
if(num1 == num2){
    num3 = 100;
}else{
    num3 = 20;
}

汇编

.data: 
num1: .word 10
num2: .word 7
num3: .word 0

.text
.globl _main
_main:
subw num1, num2
zfgoto something1
movw $20, num3
goto proend

something1:
movw $100, num3

proend:
..

.end
...

上面对num1,num2做sub后,zfgoto表示如果ZF==1(num1 == num2),也就是说, sub后,如果ZF==1可以判定num1 == num2, 这个结论在现实的数学中是成立的,同样的在计算机里也是成立的,因为计算机是人类造出来的,它一定遵循这个结论,否则没有意义,即使计算机中数据范围是有限的,在某些情况下加减会造成溢出,但也不会违背这个 两数相差0时代表两数相等的结论

然而这种判断, 只能判断 两个操作数相不相等,很多时候我们判断的情况更多,就向高级语言里if的条件, 如果我们要比较两个整数a和b的大小关系,就直接做a-b的操作,然后观察结果就ok了

假如结果是c,有这么几种情况:

情况1:

c<0 ====> a

情况2:

c>0 ====> a>b

情况3:

c=0 ====> a=b

我们先讨论一下,在数学中做减法这个过程的步骤

  • 对于情况1,因为c<0,所以a一定向他的最高位的后一位借了一位,由c<0推断出a借位,所以c<0是a借位的充分条件

  • 那么a借位能否推断出c<0呢?答案是肯定的,这里不讲什么证明的过程,我也不会,大家懂这个结论就行了,所以a借位推断出c<0

  • 继而证明c<0是a借位的充分必要条件

  • 同理c<0是a

  • 最后推断出a借位是a

这反应到计算机里,道理是一样的,假设以下的8位数的减法指令

movb a, %al
movb b, %bl
subb %bl, %al # al = al - bl 其实就是 a - b

最后的结果存储在al中,如果al在减的过程里发生借位,一定证明 a

计算机会把这个al是否借位的动作记录在一个地方,这个地方就是我们常说的标志寄存器,由于只是记录有没有这个借位的动作,所以只需要2个值就可以了,刚好0和1,具体来讲就是我们常说的CF位,所以sub后,只要CF==1就表示发生了借位,说明a


  • 对于情况2,因为c>0, 所以在减的过程里a并没有发生借位,所以c>0推断出a没有借位

  • 那么a没有借位能不能推断出c>0呢? 这个证明过程我也不会,但是大家站在常识的角度去看,当a==b的时候,减的过程也不会发生借位,所以c>0是a没有借位的充分不必要条件

  • 但是c>0却是a>b的充分必要条件,所以单单从a没有借位是不能推断出a>b的,还需要一个条件,就是c是否为0

  • 这反应到计算机里,道理还是一样的,还是上面的指令

sub后要根据2个条件才能判断出a>b

一个是CF=0(计算机记录了在减的过程中没有发生借位)

另一个是结果al是否为0

计算机里又会在一个地方记录这一标记,不多说了就是ZF位,如果ZF=0,表示指令的结果不为0,ZF=1表示指令的结果是0

所以当CF=0,并且ZF=0的时候,表示a>b

这里要注意ZF=0的时候,是不能推断出a>b的,只能表明a-b的结果不为0,不能确定a>b,因为a


  • 对于情况3,因为c=0, 可以直接判断出a==b,不用别的条件

我们来总结上面分析的结论

结论1:
如果ZF=1,那么a=b
如果CF=1, 那么a 如果CF=0 && ZF=0,那么a>b
继续推导:
如果 CF=1||ZF=1,那么a 如果 CF=0,那么a>b || a=b



根据上面的分析来仔细想想,似乎有哪些地方不对,上面的所有分析都建立在正常的情况下,好像没有考虑到计算机存储精度的条件,的确,如果考虑到计算机精度的时候,情况要稍微复杂了,但是也只是对我们认为的有符号减法有影响,无符号数还是和上面判断一样,因为上面的结论是针对于所有数的,在数学中是一单纯的数,至于是正数还是负数,判断都是一样的,由于计算机只认数,当我们把8位空间存储的内容看作是无符号数的时候,上面的判断一定成立

但是在现实中,很多地方都要有负数的概念,上面说了,计算机只认数,它不知道这个数是正还是负,所有的区分都是我们人类规定的(负数的表示就不提了),计算机并不懂,如果想让上面的结论对存储在计算机里我们认定的负数也对立,那么有一个方法就是符号位在无穷远的地方,即计算机的位数是无限的,这样的话上面的结论就会成立了,但很可惜,计算机是有精度限制的,判断我们规定的负数需要其它的条件

我们都知道,负数在计算机里是根据最高位来判断的,其实不是判断,无非是我们为了统一而规定的,如果对于一个类型的数,在它所存储的空间的最高位是1,那么规定这个数是负数,否则为正数,为了cpu的加法减法能走同一套电路,科学家设计出了补码(个人认为补码是计算机中最伟大的发明之一),当然补码被设计出来不光因为这个,这里就不提了。所有的数在计算机里存储的都是补码

还是上面的sub指令,在想判断有符号数大小关系的之前,我们一次性的将所要涉及到的标志位都先介绍一下

SF

记录某些指令执行后,结果的最高位是不是1,如果是1,那么我们可以将结果看作负数,否则为正数

OF

学名叫溢出标志位,记录在运算过程中,有没有发生溢出(溢出的概念下面会讲到)

这里请注意,很多博客都直接称OF是用来判断有符号数运算是否发生了溢出,这种说法不是说不对,是抽象化了,OF位可以帮助我们判断有符号数运算后确定大小关系的一个条件,计算机做sub的时候,OF位随时发生变化,如果用来判断无符号数,上面的结论是没有问题的,那么此时OF就没有意义,在讨论溢出之前,我们自己根据补码的特点来逆推一下OF的实际意义

假如(8位)

subb %bl,%al # al = al - bl

现在我们考虑补码,但不考虑存储位数的情况(不会发生运算结果放不下)

做完sub后

情况1

如果 SF = 1(表示al最后是负数),那么al

情况2

如果 SF=0(表示al最后是正数),那么只能说明al>bl,或者al=bl(因为结果存储在al中为0的时候SF也为0)

所以 当SF=0 && ZF=0, al>bl

上面的这2种情况是在没有考虑存储位数限制,现在加上这个限制

对于情况1
SF = 1的时候,很可能会发生在结果不够存储的情况

比如8位的时候 127+1。这个时候大家又说了,这不是一个add吗,和sub毛的关系!

我们可以直接将这个式子转换成减法 127-(-1), 这个时候因为8位的有符号数的范围是 -128~127, 自己用补码相加后结果是 -128, 最高位进位的1直接被抛弃,实际上最高位发生了进位,此时CF=1,所以此时SF=1的条件不能推导出al-1(bl)

我们在大条件是想从SF=1(代表负数)推断出al

我们考虑al和bl这样的几种情况

情况1

0 0 -127<=-bl<0
-126<=al+(-bl)<=126

很明显这个不等式不会发生结果不够存储的情况,也就是CF不可能为1

当在 -126~-1这个范围的时候 SF=1,CF=0,直接推断出al

当为 0的时候 SF=0,CF=0,推断出两个数相等 al=bl

当在 1~126的时候, SF=0,CF=0,推断出 al>bl


情况2

al>0
bl<0
很明显al>bl
al-bl(大多数情况下结果大于0)

0

对-128取补码
1000 0000 - 0000 0001 ===> 1000 0000 + 1111 11111 = 0111 1111(127) 取反还是-128,所以将bl=-128的情况先不考虑 那么 0<-bl<=127

所以 0 0

上面直接看成是2个正数相加
当在0~127这个范围的时候,没有发生结果不够存的情况
一定是SF=0, CF=0

当2个正数的和是128的时候刚好从-128开始 1000 0000(-128) ~ 1111 1110(-2)
一定是SF=1,CF=0

当bl=-128的时候, al-bl = al + (-bl==128(补码还是-128)) 范围 -127-1,和-128-2的情况一样

在以上情况里是不可能发生进位的,换言之CF不可能为1


情况3

al<0
bl<0
-128<=al<0
-128<=bl<0
0<-bl<=127(-128先不考虑)
al - bl = al + (-bl) 的范围是 -127~126

在这个过程中可能发生进位

当在 -127~-1这个范围的时候 SF=1,CF=0,可以推断出 al

当为 0的时候 SF=0,CF=1(一定发生进位,因为一个最高位是1,另一个为0,现在结果最高位是0,所以进位) 两个数相等,可以直接看ZF位

当在 1~126这个范围的时候,SF=0,CF=1,可以推断出 al>bl

当bl=-128的时候
al - bl -256~-129 始终为0(因为超出了8位的范围),SF=0,CF=1, 不能推断出a和b的关系,可能相等al和bl都为-128, 也可能al>bl


情况4

al<0
bl>0
很明显 al -128<=al<0
0 -127<=-bl<0
al - bl = al + (-bl) ====> -255~-2

当在 -255~-129的时候,结果超出范围,直接为0(一定发生进位), SF=0,CF=1

当在 -128~-2的时候, 没有超范围,但是结果为负 SF=1,CF=0


上面4种情况 al bl的范围分析,是所有al和bl同时不为0的情况,下面是有0的情况,CF不可能进位,所以是0

当al=0,bl=0, al==bl, SF=0,CF=0

当al=0,bl>0 al

当al=0,bl<0 al>bl, -128<=bl<0 0<-bl<=127(SF=0,CF=0)...-128(SF=1,CF=0)

当al>0,bl=0 al>bl, 0

当al<0,bl=0,al

将上面所有的情况列出来

SF=1,CF=0 ===> al SF=0,CF=0 ===> al>bl
SF=0,CF=0,(ZF=1) ===> al=bl
SF=0,CF=0 ===> al>bl
SF=1,CF=0 ===> al>bl
SF=1,CF=0 ===> al SF=0,CF=1,(ZF=1) ===> al=bl
SF=0,CF=1 ===> al>=bl
SF=0,CF=1 ===> al SF=1,CF=0 ===> al SF=0,CF=0 ===> al=bl
SF=1,CF=1 ===> al SF=0,CF=0 || SF=1,CF=0 ===> al>bl
SF=0,CF=0 ===> al>bl
SF=1,CF=0 ===> al

再次整理

al SF=1,CF=0
SF=0,CF=1
SF=1,CF=1

al>bl时
SF=0,CF=0
SF=1,CF=0
SF=0,CF=1

很明显不能根据SF和CF来判断有符号数的大小关系,那接下来要怎么整呢?

这个时候,我们自己来分析下,自己搞个规则来判断

正常情况下我们还是需要标志位的,所以对有符号数的判断其中之一要有SF,我们定义一个标志位假如是 FF, FF主要目的是记录什么时候发生超出范围,假设我们现在规定FF=1的时候,表示运算结果超出范围(-128~127之外),FF=0表示正常情况没有超出范围

如果在正常的情况下,即没有发生超出范围的情况(其中包括了al和bl有0的情况)

SF=1,FF=0 ===>al SF=0,FF=0 ===>al>=bl

回过头观察超出范围的情况只有2种情况

  • al>0, bl<0的时候 SF=1,FF=1 ===> al>bl
  • al<0 bl>0的时候 SF=0,FF=1 al

即 FF!=SF的时候 al FF=SF的时候 al>=bl

从这2点结论可以明显的推导出al和bl的大小关系,现在回到最开始的计算机的OF,很多博客说OF的原理是判断有符号位加法时2数的大小,根据书本上学到的结论将上述的FF换成OF,就是结论,所以溢出的概念是计算机在进行加法运算的时候超出规定范围的时候(eg:8位 -128~127)会被置位,和CF没有任何关系

要注意一点理解溢出不要用笔拿写出2个操作数然后补码运算,就按实际的立即 只要运算的结果没有在规定的范围内,就是溢出,比如 -1+1,如果用笔写出补码去算 发现8位的不够存储,最高位(CF)发生了进位,但实际上由于结果是0所以OF是不会置1的,至于电路是怎么实现的,我就不知道了,反正你只要按照运算的结果在没在范围内就行了

下面列出有符号和无符号的跳转指令(i386架构)

有符号数

SF!=OF

JL (小于就跳转,和上面自定义时分析的FF是一样的)

JNGE (不大于 或 不等于 就跳转, 其实就是“小于”的另一种说法)

SF = OF

JGE(大于 或者 等于 就跳转,和上面FF分析的情况也是一样的)

JNL(如果不小于就跳转 就是 “大于或等于” 的另一种说法)

ZF=1 or SF!=OF

JLE(小于 或 等于 就跳转, 这个自己理解,就是讲上面分析的条件综合一起做更细微的大小关系判断)

JNG(不大于 就跳转 和JLE是同一种目的)

ZF=0 and SF!=OF

JG (大于就跳转)

JNLE(和JG一样)

无符号数

CF=1

JB(小于就跳转 和上面分析的一样,只看CF就可以判断a和b的大小)

JC(表示只要CF=1,就跳转)

JNAE(不大于 或 不等于 其实就是 JB的说法)

CF=0

JAE(大于等于 就跳转,还是和上面分析的一样)

JNB(不低于 和JAE的说法一样)

JNC(CF没有Z置位,即CF=0的时候 跳转)

CF=1 or ZF=1

JBE(小于 或 等于)

JNA(不高于 和 JBE同样的道理)

CF=0 and ZF=0

JA(大于 就 跳转)

JNBE(不小于 或 不等于 和JA一样的性质)

\

你可能感兴趣的:(高级条件if的底层原理)