布爾表達式
在高級語言中 布爾值用來做條件判斷從而對程序走向作出控制,其中涉及到的運算符
&&
對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<b
情況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<b的充分必要條件
最后推斷出a借位是a<b的充分必要條件
這反應到計算機里,道理是一樣的,假設以下的8位數的減法指令
movb a, %al
movb b, %bl
subb %bl, %al # al = al - bl 其實就是 a - b
最后的結果存儲在al中,如果al在減的過程里發生借位,一定證明 a<b
計算機會把這個al是否借位的動作記錄在一個地方,這個地方就是我們常說的標志寄存器,由于只是記錄有沒有這個借位的動作,所以只需要2個值就可以了,剛好0和1,具體來講就是我們常說的CF位,所以sub后,只要CF==1就表示發生了借位,說明a<b
對于情況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<b時ZF也為0
- 對于情況3,因為c=0, 可以直接判斷出a==b,不用別的條件
我們來總結上面分析的結論
結論1:
如果ZF=1,那么a=b
如果CF=1, 那么a<b
如果CF=0 && ZF=0,那么a>b
繼續推導:
如果 CF=1||ZF=1,那么a<b||a=b
如果 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<bl
情況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<bl,因為這個情況很明顯 127(al)>-1(bl)
我們在大條件是想從SF=1(代表負數)推斷出al<bl
我們考慮al和bl這樣的幾種情況
情況1
0<al<=127
0<bl<=127
-127<=-bl<0
-126<=al+(-bl)<=126
很明顯這個不等式不會發生結果不夠存儲的情況,也就是CF不可能為1
當在 -126~-1這個范圍的時候 SF=1,CF=0,直接推斷出al<bl,因為兩正數相差,結果為負
當為 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<al<=127 -128<=bl<0 0<-bl<=-(-128)
對-128取補碼
1000 0000 - 0000 0001 ===> 1000 0000 + 1111 11111 = 0111 1111(127) 取反還是-128,所以將bl=-128的情況先不考慮 那么 0<-bl<=127
所以 0<al+(-bl)<=254,根據8位的范圍,當超過127后,會從-128開始遞增,將這個不等式拆分一下
0<al+(-bl)<=127 .... (-128~-2), 這個-2的算法(-128+(254-128=126)=-2)
上面直接看成是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<bl, 兩個負數相差為負
當為 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<bl
-128<=al<0
0<bl<=127
-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<bl, al-bl的范圍就是-bl的范圍 -127~-1, SF=1,CF=0
當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<=127(SF=0,CF=0)
當al<0,bl=0,al<bl, -128<al<0(SF=1,CF=0)
將上面所有的情況列出來
SF=1,CF=0 ===> al<bl
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<bl
SF=0,CF=1,(ZF=1) ===> al=bl
SF=0,CF=1 ===> al>=bl
SF=0,CF=1 ===> al<bl
SF=1,CF=0 ===> al<bl
SF=0,CF=0 ===> al=bl
SF=1,CF=1 ===> al<bl
SF=0,CF=0 || SF=1,CF=0 ===> al>bl
SF=0,CF=0 ===> al>bl
SF=1,CF=0 ===> al<bl
再次整理
al<bl時
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<bl
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<bl
即 FF!=SF的時候 al<bl
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一樣的性質)
\