王爽《匯編語言》第四版 超級筆記
第11章 標志寄存器
CPU內部的寄存器中,有一種特殊的寄存器(對於不同的處理機,個數和結構都可能不同)具有以下3種作用。
(1)用來存儲相關指令的某些執行結果;
(2)用來為CPU執行相關指令提供行為依據;
(3)用來控制CPU的相關工作方式。
這種特殊的寄存器在8086CPU中,被稱為標志寄存器。8086CPU的標志寄存器有16位,其中存儲的信息通常被稱為程序狀態字(PSW)。
我們己經使用過8086CPU的ax、bx、cx、dx、si、di、bp、sp、IP、cs、ss、ds、es等13個寄存器了,本章中的標志寄存器(以下簡稱為flag)是我們要學習的最后一個寄存器。
flag和其他寄存器不一樣,其他寄存器是用來存放數據的,都是整個寄存器具有一個含義。
而flag寄存器是按位起作用的,也就是說,它的每一位都有專門的含義,記錄特定的信息。
8086CPU的flag寄存器的結構如圖11.1所示。
flag的1、3、5、12、13、14、15位在8086CPU中沒有使用,不具有任何含義。而0、2、4、6、7、8、9、10、11位都具有特殊的含義。
在這一章中,我們學習標志寄存器中的CF、PF、ZF、SF、OF、DF標志位,以及一些與其相關的典型指令。
11.1 ZF標志、PF標志、SF標志
flag的第6位是ZF,零標志位。它記錄相關指令執行后,其結果是否為0。如果結果為0,那么zf=1;如果結果不為0,那么zf=0。
比如,指令:
mov ax,1
sub ax,1
執行后,結果為0,則zf=1。
mov ax,2
sub ax,1
執行后,結果不為0,則zf=0。
對於zf的值,我們可以這樣來看,zf標記相關指令的計算結果是否為0,如果為0,則zf要記錄下“是0”這樣的肯定信息。
在計算機中1表示邏輯真,表示肯定,所以當結果為0的時候zf=1,表示“結果是0”。如果結果不為0,則zf要記錄下“不是0”這樣的否定信息。
在計算機中0表示邏輯假,表示否定,所以當結果不為0的時候zf=0,表示“結果不是0”。
比如,指令:
mov ax,1
and ax,0
執行后,結果為0,則zf=1,表示“結果是0”。
mov ax,1
or ax,0
執行后,結果不為0,則zf=0,表示“結果非0”。
注意,在8086CPU的指令集中,有的指令的執行是影響標志寄存器的,比如,add、sub、mul、div、inc、or、and等,它們大都是運算指令(進行邏輯或算術運算);
有的指令的執行對標志寄存器沒有影響,比如,mov、push、pop等,它們大都是傳送指令。在使用一條指令的時候,要注意這條指令的全部功能,其中包括,執行結果對標志寄存器的哪些標志位造成影響。
flag的第2位是PF,奇偶標志位。它記錄相關指令執行后,其結果的所有bit位中1的個數是否為偶數。如果1的個數為偶數,pf=1,如果為奇數,那么pf=0。
比如,指令:
mov al,1
add al,10
執行后,結果為00001011B,其中有3(奇數)個1,則pf=0;
mov al,1
or al,2
執行后,結果為00000011B,其中有2(偶數)個1,則pf=1;
sub al,al
執行后,結果為00000000B,其中有0(偶數)個1,則pf=1。
flag的第7位是SF,符號標志位。它記錄相關指令執行后,其結果是否為負。如果結果為負,sf=1;如果非負,sf=0。
計算機中通常用補碼來表示有符號數據。計算機中的一個數據可以看作是有符號數, 也可以看成是無符號數。比如:
- 00000001B,可以看作為無符號數1,或有符號數+1;
- 10000001B,可以看作為無符號數129,也可以看作有符號數-127。
這也就是說,對於同一個二進制數據,計算機可以將它當作無符號數據來運算,也可以當作有符號數據來運算。比如:
mov al,10000001B
add al,1
結果:(al)=10000010B。
可以將add指令進行的運算當作無符號數的運算,那么add指令相當於計算129+1,結果為130(10000010B);也可以將add指令進行的運算當作有符號數的運算,那么add指令相當於計算-127+1,結果為-126(10000010B)。
不管我們如何看待,CPU在執行add等指令的時候,就己經包含了兩種含義,也將得到用同一種信息來記錄的兩種結果。關鍵在於我們的程序需要哪一種結果。
SF標志,就是CPU對有符號數運算結果的一種記錄,它記錄數據的正負。在我們將數據當作有符號數來運算的時候,可以通過它來得知結果的正負。如果我們將數據當作無符號數來運算,SF的值則沒有意義,雖然相關的指令影響了它的值。
這也就是說,CPU在執行add等指令時,是必然要影響到SF標志位的值的。至於我們需不需要這種影響,那就看我們如何看待指令所進行的運算了。
比如:
mov al,10000001B
add al,1
執行后,結果為10000010B,sf=1,表示:如果指令進行的是有符號數運算,那么結果為負;
mov al,10000001B
add al,01111111B
執行后,結果為0,sf=0,表示:如果指令進行的是有符號數運算,那么結果為非負。
某些指令將影響標志寄存器中的多個標記位,這些被影響的標記位比較全面地記錄了指令的執行結果,為相關的處理提供了所需的依據。
比如指令sub al,al執行后,ZF、PF、SF等標志位都要受到影響,它們分別為:1、1、0。
11.2 CF標志、OF標志
flag的第0位是CF,進位標志位。一般情況下,在進行無符號數運算的時候,它記錄了運算結果的最高有效位向更高位的進位值,或從更高位的借位值。
對於位數為N的無符號數來說,其對應的二進制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相對於最高有效位的更高位,如圖11.2 所示。
我們知道,當兩個數據相加的時候,有可能產生從最高有效位向更高位的進位。比如,兩個8位數據:98H+98H,將產生進位。由於這個進位值在8位數中無法保存,我們在前面的課程中,就只是簡單地說這個進位值丟失了。
其實CPU在運算的時候,並不丟棄這個進位值,而是記錄在一個特殊的寄存器的某一位上。
8086CPU就用flag的CF位來記錄這個進位值。比如,下面的指令:
mov al,98H
add al,al ;執行后:(al)=30H, CF=1, CF記錄了從最高有效位向更高位的進位值
add al,al ;執行后:(al)=60H, CF=0, CF記錄了從最高有效位向更高位的進位值
而當兩個數據做減法的時候,有可能向更高位借位。比如,兩個8位數據:97H-98H,將產生借位,借位后,相當於計算197H-98H。而flag的CF位也可以用來記錄這個借位值。比如,下面的指令:
mov al,97H
sub al,98H ;執行后:(al)=FFH, CF=1, CF記錄了向更高位的借位值
sub al,al ;執行后:(al)=0, CF=0, CF記錄了向更高位的借位值
我們先來談談溢出的問題。在進行有符號數運算的時候,如結果超過了機器所能表示的范圍稱為溢出。
那么,什么是機器所能表示的范圍呢?
比如說,指令運算的結果用8位寄存器或內存單元來存放,比如,add al,3,那么對於8位的有符號數據,機器所能表示的范圍就是-128127。同理,對於16位有符號數據,機器所能表示的范圍是-3276832767。
如果運算結果超出了機器所能表達的范圍,將產生溢出。
注意,這里所講的溢出,只是對有符號數運算而言。下面我們看兩個溢出的例子。
mov al,98
add al,99
執行后將產生溢出。因為add al,99進行的有符號數運算是:
(al)=(al)+99=98+99=197。
而結果197超出了機器所能表示的8位有符號數的范圍:-128~127。
mov al,0F0H ;F0H,為有符號數-16的補碼
add al,088H ;88H,為有符號數-120的補碼
執行后,將產生溢出。因為add al,088H進行的有符號數運算是:
(al)=(al)+(-l 20)=(-16)+(-120)=-136
而結果-136超出了機器所能表示的8位有符號數的范圍:-128~127。
如果在進行有符號數運算時發生溢出,那么運算的結果將不正確。就上面的兩個例子來說:
mov al,98
add al,99
add指令運算的結果是(al)=0C5H,因為進行的是有符號數運算,所以al中存儲的是有符號數,而C5H是有符號數-59的補碼。
如果我們用add指令進行的是有符號數運算,則98+99=-59這樣的結果讓人無法接受。造成這種情況的原因,就是實際的結果197,作為一個有符號數,在8位寄存器al中存放不下。
同樣,對於:
mov al,0F0H ;F0H,為有符號數-16的補碼
add al,088H ;88H,為有符號數-120的補碼
add指令運算的結果是(al)=78H,因為進行的是有符號數運算,所以al中存儲的是有符號數,而78H表示有符號數120。如果我們用add指令進行的是有符號數運算,則-16-120=120這樣的結果顯然不正確。造成這種情況的原因,就是實際的結果-136,作為一個有符號數,在8位寄存器al中存放不下。
由於在進行有符號數運算時,可能發生溢出而造成結果的錯誤。則CPU需要對指令執行后是否產生溢出進行記錄。
flag的第11位是OF,溢出標志位。一般情況下,OF記錄了有符號數運算的結果是否發生了溢出。如果發生溢出,OF=1;如果沒有,OF=0。
一定要注意CF和OF的區別:CF是對無符號數運算有意義的標志位,而OF是對有符號數運算有意義的標志位。 比如:
mov al,98
add al,99
add指令執行后:CF=0,OF=1。前面我們講過,CPU在執行add等指令的時候,就包含了兩種含義:無符號數運算和有符號數運算。
對於無符號數運算,CPU用CF位來記錄是否產生了進位;對於有符號數運算,CPU用OF位來記錄是否產生了溢出,當然,還要用SF位來記錄結果的符號。
對於無符號數運算,98+99沒有進位,CF=0;對於有符數運算,98+99發生溢出,OF=1。
mov al,0F0H
add al,88H
add指令執行后:CF=1,OF=1。對於無符號數運算,0F0H+88H有進位,CF=1;對於有符號數運算,0F0H+88H發生溢岀,OF=1。
mov al,0F0H
add al,78H
add指令執行后:CF=1,OF=0。對於無符號運算,0F0H+78H有進位,CF=1;對於有符號數運算,0F0H+78H不發生溢出,OF=0。
我們可以看出,CF和OF所表示的進位和溢出,是分別對無符號數和有符號數運算而言的,它們之間沒有任何關系。
11.3 adc指令、sbb指令、cmp指令
adc是帶進位加法指令,它利用了CF位上記錄的進位值。
指令格式:adc 操作對象1,操作對象2
功能:操作對象1=操作對象1+操作對象2+CF
比如指令adc ax,bx實現的功能是:(ax)=(ax)+(bx)+CF
例:
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
執行后,(ax)=4。adc執行時,相當於計算:(ax)+1+CF=2+1+1=4。
mov ax,1
add ax,ax
adc ax,3
執行后,(ax)=5。adc執行時,相當於計算:(ax)+3+CF=2+3+0=5。
mov al,98H
add al,al
adc al,3
執行后,(al)=34H。adc執行時,相當於計算:(al)+3+CF=30H+3+1=34H。
可以看出,adc指令比add指令多加了一個CF位的值。
為什么要加上CF的值呢?CPU為什么要提供這樣一條指令呢?
先來看一下CF的值的含義。在執行adc指令的時候加上的CF的值的含義,是由adc指令前面的指令決定的,也就是說,關鍵在於所加上的CF值是被什么指令設置的。
顯然,如果CF的值是被sub指令設置的,那么它的含義就是借位值;如果是被add指令設置的,那么它的含義就是進位值。我們來看一下兩個數據:0198H和0183H如何相加的:
可以看出,加法可以分兩步來進行:
①低位相加;
②高位相加再加上低位相加產生的進位值。
下面的指令和add ax,bx具有相同的結果:
add al,bl
adc ah,bh
看來CPU提供adc指令的目的,就是來進行加法的第二步運算的。adc指令和add指令相配合就可以對更大的數據進行加法運算。我們來看一個例子:
編程,計算1EF000H+201000H,結果放在ax(高16位)和bx(低16位)中。
因為兩個數據的位數都大於16,用add指令無法進行計算。我們將計算分兩步進行,先將低16位相加,然后將高16位和進位值相加。程序如下。
mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H
adc指令執行后,也可能產生進位值,所以也會對CF位進行設置。由於有這樣的功能,我們就可以對任意大的數據進行加法運算。看一個例子:
編程,計算1EF0001000H+2010001EF0H,結果放在ax(最高16位),bx(次高16位),cx(低16位)中。
計算分3步進行:
(1)先將低16位相加,完成后,CF中記錄本次相加的進位值;
(2)再將次高16位和CF(來自低16位的進位值)相加,完成后,CF中記錄本次相加的進位值;
(3)最后高16位和CF(來自次高16位的進位值)相加,完成后,CF中記錄本次相加的進位值。
程序如下。
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
adc bx,1000H
adc ax,0020H
下面編寫一個子程序,對兩個128位數據進行相加。
名稱:addl28
功能:兩個128位數據進行相加。
參數:ds:si指向存儲第一個數的內存空間,因數據為128位,所以需要8個字單元,由低地址單元到高地址單元依次存放128位數據由低到高的各個字。運算結果存儲在第一個數的存儲空間中。
ds:di指向存儲第二個數的內存空間。
程序如下。
addl28: push ax
push cx
push si
push di
sub ax,ax ;將CF設置為0
mov cx,8
s:mov ax,[si]
adc ax,[di]
mov [si],ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop cx
pop ax
ret
inc和loop指令不影響CF位。
sbb是帶借位減法指令,它利用了CF位上記錄的借位值。
指令格式:sbb操作對象1,操作對象2
功能:操作對象1=操作對象1-操作對象2-CF
比如指令sbb ax,bx實現的功能是:(ax)=(ax)-(bx)-CF
sbb指令執行后,將對CF進行設置。利用sbb指令可以對任意大的數據進行減法運算。比如,計算003E1000H-00202000H,結果放在ax,bx中,程序如下:
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H
sbb和adc是基於同樣的思想設計的兩條指令,在應用思路上和adc類似。
在這里,我們就不再進行過多的討論。通過學習這兩條指令,我們可以進一步領會一下標志寄存器CF位的作用和意義。
cmp是比較指令,cmp的功能相當於減法指令,只是不保存結果。
cmp指令執行后,將對標志寄存器產生影響。其他相關指令通過識別這些被影響的標志寄存器位來得知比較結果。
cmp指令格式:cmp操作對象1,操作對象2
功能:計算操作對象1-操作對象2但並不保存結果,僅僅根據計算結果對標志寄存器進行設置。
比如,指令cmp ax,ax,做(ax)-(ax)的運算,結果為0,但並不在ax中保存,僅影響flag的相關各位。指令執行后:zf=1,pf=1,sf=0,cf=0,of=0。
下面的指令:
mov ax,8
mov bx,3
cmp ax,bx
執行后:(ax)=8,zf=0,pf=1,sf=0,cf=0,of=0。
其實,我們通過cmp指令執行后,相關標志位的值就可以看岀比較的結果。
cmp ax,bx
- 如果(ax)=(bx) 則(ax)-(bx)=0,所以:zf=1;
- 如果(ax)不等於(bx) 則(ax)-(bx)不等於0,所以:zf=0;
- 如果(ax)<(bx) 則(ax)-(bx)將產生借位,所以:cf=1;
- 如果(ax)>=(bx) 則(ax)-(bx)不必借位,所以:cf=0;
- 如果(ax)>(bx ) 則(ax)-(bx)既不必借位,結果又不為0,所以:cf=0並且zf=0;
- 如果(ax)<=(bx) 則(ax)-(bx)既可能借位,結果可能為0,所以:cf=1或zf=1。
現在我們可以看出比較指令的設計思路,即:通過做減法運算,影響標志寄存器,標志寄存器的相關位記錄了比較的結果。反過來看上面的例子。
指令cmp ax,bx的邏輯含義是比較ax和bx中的值,如果執行后:
- zf=1,說明(ax)=(bx)
- zf=0,說明(ax)不等於(bx)
- cf=1,說明(ax)<(bx)
- cf=0,說明(ax)>=(bx)
- cf=0並且zf=0,說明(ax)>(bx)
- cf=1或zf=1,說明(ax)<=(bx)
同add、sub指令一樣,CPU在執行cmp指令的時候,也包含兩種含義:進行無符號數運算和進行有符號數運算。
所以利用cmp指令可以對無符號數進行比較,也可以對有符號數進行比較。上面所講的是用cmp進行無符號數比較時,相關標志位對比較結果的記錄。下面我們再來看一下如果用cmp來進行有符號數比較時,CPU用哪些標志位對比較結果進行記錄。
我們以cmp ah,bh為例進行說明。
cmp ah,bh
- 如果(ah)=(bh) 則(ah)-(bh)=0,所以:zf=1;
- 如果(ah)不等於(bh) 則(ah)-(bh)不等於0,所以:zf=0;
所以,根據cmp指令執行后zf的值,就可以知道兩個數據是否相等。
我們繼續看,如果(ah)<(bh)則可能發生什么情況呢?
對於有符號數運算,在(ah)<(bh)情況下,(ah)-(bh)顯然可能引起sf=1,即結果為負。
比如:
- (ah)=1,(bh)=2;則(ah)-(bh)=0FFH,0FFH為-1的補碼,因為結果為負,所以 sf=1。
- (ah)=0FEH,(bh)=0FFH;則(ah)-(bh)=-2-(-1)=0FFH,因為結果為負,所以 sf=1。
通過上面的例子,我們是不是可以得到這樣的結論:“cmp 操作對象1,操作對象2”指令執行后,sf=1,就說明操作對象1<操作對象2?
當然不是。
我們再看兩個例子。
(ah)=22H,(bh)=0A0H;則(ah)-(bh)=34-(-96)=82H,82H是-126的補碼
所以sf=1
這里雖然sf=1,但是並不能說明(ah)<(bh)因為顯然34>-96。
兩個有符號數A和B相減,得到的是負數,那么可以肯定A<B,這個思路沒有錯誤,關鍵在於我們根據什么來斷定得到的是一個負數。
CPU將cmp指令得到的結果記錄在flag的相關標志位中。我們可以根據指令執行后,相關標志位的值來判斷比較的結果。單純地考查sf的值不可能知道結果的正負。因為sf記錄的只是可以在計算機中存放的相應位數的結果的正負。
比如add ah,al執行后,sf記錄的是ah中的8位二進制信息所表示的數據的正負。cmp ah,bh執行后,sf記錄的是(ah)-(bh)所得到的8位結果數據的正負,雖然這個結果沒有在我們能夠使用的寄存器或內存單元中保存,但是在指令執行的過程中,它暫存在CPU內部的暫存器中。
所得到的相應結果的正負,並不能說明,運算所應該得到的結果的正負。這是因為在運算的過程中可能發生溢出。如果有這樣的情況發生,那么,sf的值就不能說明任何問題。比如:
mov ah,22H
mov bh,0A0H
sub ah,bh
結果sf=1,運算實際得到的結果是(ah)=82H,但是在邏輯上,運算所應該得到的結果是:34-(-96)=130。就是因為130這個結果作為一個有符號數超岀了-128~127這個范圍,在ah中不能表示,而ah中的結果被CPU當作有符號數解釋為-126。而sf被用來記錄這個實際結果的正負,所以sf=1。但sf=1不能說明在邏輯上,運算所得的正確結果的正負。
我們考慮一下,兩種結果之間的關系,實際結果的正負,和邏輯上真正結果的正負,它們之間有多大的距離呢?
從上面的分析中,我們知道,實際結果的正負,之所以不能說明邏輯上真正結果的正負,關鍵的原因在於發生了溢岀。如果沒有溢出發生的話,實際結果的正負和邏輯上真正結果的正負就一致了。
所以,我們應該在考查sf(得知實際結果的正負)的同時考查(得知有沒有溢出),就可以得知邏輯上真正結果的正負,同時就可以知道比較的結果。
下面,我們以cmp ah,bh為例,總結一下CPU執行cmp指令后,sf和of的值是如何來說明比較的結果的。
(1)如果sf=1,而of=0
of=0,說明沒有溢出,邏輯上真正結果的正負=實際結果的正負;
因sf=1,實際結果為負,所以邏輯上真正的結果為負,所以(ah)<(bh)。
(2)如果sf=1,而of=1
of=1,說明有溢出,邏輯上真正結果的正負不等於實際結果的正負;
因sf=1,實際結果為負。
實際結果為負,而又有溢出,這說明是由於溢出導致了實際結果為負,簡單分析一下,就可以看岀,如果因為溢出導致了實際結果為負,那么邏輯上真正的結果必然為正。
這樣,sf=1,of=1,說明了(ah)>(bh)。
(3)如果sf=0,而of=1
of=1,說明有溢出,邏輯上真正結果的正負不等於實際結果的正負;
因sf=0,實際結果非負。而of=1說明有溢岀,則結果非0,所以,實際結果為正。
實際結果為正,而又有溢出,這說明是由於溢出導致了實際結果非負,簡單分析一下,就可以看岀,如果因為溢出導致了實際結果為正,那么邏輯上真正的結果必然為負。
這樣,sf=0,of=1,說明了(ah)<(bh)。
(4)如果sf=0,而of=0
of=0,說明沒有溢出,邏輯上真正結果的正負=實際結果的正負;
因sf=0,實際結果非負,所以邏輯上真正的結果非負,所以(ah)>=(bh)。
我們深入討論了cmp指令在進行有符號數和無符號數比較時,對flag相關標志位的影響和CPU如何通過相關的標志位來表示比較的結果。
在學習中,要注意領會8086CPU這種工作機制的設計思想。實際上,這種設計思想對於各種處理機來說是普遍的。
下面的內容中我們將學習一些根據cmp指令的比較結果(即cmp指令執行后,相關標志位的值)進行工作的指令。
11.4 檢測比較結果的條件轉移指令、DF標志和串傳送指令
“轉移”指的是它能夠修改IP,而“條件”指的是它可以根據某種條件,決定是否修改IP。
比如,jcxz就是一個條件轉移指令,它可以檢測cx中的數值,如果(cx)=0,就修改IP,否則什么也不做。所有條件轉移指令的轉移位移都是[-128,127]。
除了jcxz之外,CPU還提供了其他條件轉移指令,大多數條件轉移指令都檢測標志寄存器的相關標志位,根據檢測的結果來決定是否修改IP。
它們檢測的是哪些標志位呢?
就是被cmp指令影響的那些,表示比較結果的標志位。這些條件轉移指令通常都和cmp相配合使用,就好像call和ret指令通常相配合使用一樣。
因為cmp指令可以同時進行兩種比較,無符號數比較和有符號數比較,所以根據cmp指令的比較結果進行轉移的指令也分為兩種,即根據無符號數的比較結果進行轉移的條件轉移指令(它們檢測zf、cf的值)和根據有符號數的比較結果進行轉移的條件轉移指令(它們檢測sf、of和zf的值)。
下面是常用的根據無符號數的比較結果進行轉移的條件轉移指令。
這些指令比較常用,它們都很好記憶,它們的第一個字母都是j,表示jump;后面的字母表示意義如下。
e:表不 equal
ne:表示 not equal
b:表示 below
nb:表不 not below
a:表不 above
na:表示 not above
注意觀察一下它們所檢測的標志位,都是cmp指令進行無符號數比較的時候,記錄比較結果的標志位。
比如je,檢測zf位,當zf=1的時候進行轉移,如果在je前面使用了cmp指令,那么je對zf的檢測,實際上就是間接地檢測cmp的比較結果是否為兩數相等。下面看一個例子。
編程實現如下功能:
如果(ah)=(bh)則(ah)=(ah)+(ah),否則(ah)=(ah)+(bh)。
cmp ah,bh
je s
add ah, bh
jmp short ok
s:add ah,ah
ok: ...
上面的程序執行時,如果(ah)=(bh),則cmp ah,bh使zf=1,而je檢測zf是否為1,如果為1,將轉移到標號s處執行指令add ah,ah。這也可以說,cmp比較ah、bh后所得到的相等的結果使得je指令進行轉移。從而很好地體現了je指令的邏輯含義,相等則轉移。
雖然je的邏輯含義是“相等則轉移”,但它進行的操作是zf=1時則轉移。“相等則轉移”這種邏輯含義,是通過和cmp指令配合使用來體現的,因為是cmp指令為“zf=1”賦予了“兩數相等”的含義。
至於究竟在je之前使不使用cmp指令,在於我們的安排。je檢測的是zf位置,不管je前面是什么指令,只要CPU執行je指令時,zf=1,那么就會發生轉移,比如:
mov ax,0
add ax,0
je s
inc ax
s: inc ax
執行后,(ax)=1。add ax,0使得zf=1,所以je指令將進行轉移。可在這個時候發生的轉移的確不帶有“相等則轉移”的含義。
因為此處的je指令檢測到的zf=1,不是由cmp等比較指令設置的,而是由add指令設置的,並不具有“兩數相等”的含義。但無論“zf=1”的含義如何,是什么指令設置的,只要是zf=1,就可以使得je指令發生轉移。
對於jne、jb、jnb、ja、jna等指令和cmp指令配合使用的思想和je相同,可以自己分析一下。
雖然我們分別討論了cmp指令和與其比較結果相關的有條件轉移指令,但是它們經常在一起配合使用。
所以我們在聯合應用它們的時候,不必再考慮cmp指令對相關標志位的影響和je等指令對相關標志位的檢測。因為相關的標志位,只是為cmp和je等指令傳遞比較結果。
我們可以直接考慮cmp和je等指令配合使用時,表現出來的邏輯含義。它們在聯合使用的時候表現出來的功能有些像高級語言中的IF語句。
我們來看下面的一組程序。
data段中的8個字節如下:
data segment
db 8,11,8,1,8,5,63,38
data ends
(1)編程,統計data段中數值為8的字節的個數,用ax保存統計結果。
編程思路:初始設置(ax)=0,然后用循環依次比較每個字節的值,找到一個和8相等的數就將ax的值加1。程序如下。
mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一個字節
mov ax,0 ;初始化累加器
mov cx,8
s: cmp byte ptr [bx],8 ;和8進行比較
jne next ;如果不相等轉到next,繼續循環
inc ax ;如果相等就將計數值加1
next: inc bx
loop s ;程序執行后:(ax)=3
這個程序也可以寫成這樣:
mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一個字節
mov ax,0 ;初始化累加器
mov cx,8
s: cmp byte ptr [bx],8 ;和8進行比較
je ok ;如果相等轉到ok
jmp short next ;如果不相等轉到next,繼續循環
inc ax ;如果相等就將計數值加1
ok: inc ax
next: inc bx
loop s ;程序執行后:(ax)=3
比起第一個程序,它直接地遵循了“等於8則計數值加1”的原則,用je指令檢測等於8的情況,但是沒有第一個程序精簡。第一個程序用jne檢測不等於8的情況,從而間接地檢測等於8的情況。要注意在使用cmp和條件轉移指令時的這種編程思想。
(2)編程,統計data段中數值大於8的字節的個數,用ax保存統計結果。
編程思路:初始設置(ax)=0,然后用循環依次比較每個字節的值,找到一個大於8的就將ax的值加1。
程序如下。
mov ax,data
mov ds,ax
mov ax,0 ;初始化累加器
mov bx,0 ;ds:bx指向第一個字節
mov cx,8
s: cmp byte ptr [bx],8 ;和8進行比較
jna next ;如果不大於8轉到next,繼續循環
inc ax ;如果大於8就將計數值加1
next: inc bx
loop s ;程序執行后:(ax)=3
程序執行后:(ax)=3
(3)編程,統計data段中數值小於8的字節的個數,用ax保存統計結果。
編程思路:初始設置(ax)=0,然后用循環依次比較每個字節的值,找到一個小於8的就將ax的值加1。程序如下。
mov ax,data
mov ds,ax
mov ax,0 ;初始化累加器
mov bx,0 ;ds:bx指向第一個字節
mov cx,8
s: cmp byte ptr [bx],8 ;和8進行比較
jnb next ;如果不小於8轉到next,繼續循環
inc ax ;如果小於8就將計數值加1
next: inc bx
loop s
程序執行后:(ax)=2
上面講解了根據無符號數的比較結果進行轉移的條件轉移指令。根據有符號數的比較結果進行轉移的條件轉移指令的工作原理和無符號的相同,只是檢測了不同的標志位。
我們在這里主要探討的是cmp、標志寄存器的相關位、條件轉移指令三者配合應用的原理,這個原理具有普遍性,而不是逐條講解條件轉移指令。對這些指令感興趣的讀者可以查看相關的指令手冊。
flag的第10位是DF,方向標志位。在串處理指令中,控制每次操作后si、di的增減。
- df=0 每次操作后si、di遞增;
- df=1 每次操作后si、di遞減。
我們來看下面的一個串傳送指令。
格式:movsb
功能:執行movsb指令相當於進行下面幾步操作。
(1)((es)x16+(di))=((ds)x16+(si))
(2)如果df=0 則:
(si)=(si)+1
(di)=(di)+1
如果df=1 則:
(si)=(si)-1
(di)=(di)-1
用匯編語法描述movsb的功能如下。
mov es:[di],byte ptr ds:[si] ;8086並不支持這樣的指令,這里只是個描述
如果df=0:
inc si
inc di
如果df=1:
dec si
dec di
可以看出,movsb的功能是將ds:si指向的內存單元中的字節送入es:di中,然后根據標志寄存器df位的值,將si和di遞增或遞減。
當然,也可以傳送一個字,指令如下。
格式:movsw
movsw的功能是將ds:si指向的內存字單元中的字送入cs:di中,然后根據標志寄存器df位的值,將si和di遞增2或遞減2。
用匯編語法描述movsw的功能如下。
mov es:[di],word ptr ds:[si] ;8086並不支持這樣的指令,這里只是個描述
如果df=0:
add si,2
add di,2
如果df=1:
sub si,2
sub di,2
movsb和movsw進行的是串傳送操作中的一個步驟,一般來說,movsb和movsw都和rep配合使用,格式如下:
rep movsb
用匯編語法來描述rep movsb的功能就是:
s: movsb
loop s
可見,rep的作用是根據cx的值,重復執行后面的串傳送指令。由於每執行一次movsb指令si和di都會遞增或遞減指向后一個單元或前一個單元,則rep movsb就可以循環實現(cx)個字符的傳送。
同理,也可以使用這樣的指令:rep movsw。
相當於:
s: movsw
loop s
由於flag的df位決定着串傳送指令執行后,si和di改變的方向,所以CPU應該提供相應的指令來對df位進行設置,從而使程序員能夠決定傳送的方向。
8086CPU提供下面兩條指令對df位進行設置。
- cld指令:將標志寄存器的df位置0
- std指令:將標志寄存器的df位置1
我們來看下面的兩個程序。
(1)編程,用串傳送指令,將data段中的第一個字符串復制到它后面的空間中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
我們分析一下,使用串傳送指令進行數據的傳送,需要給它提供一些必要的信息,它們是:
- 傳送的原始位置:ds:si;
- 傳送的目的位置:es:di;
- 傳送的長度:cx;
- 傳送的方向:df。
在這個問題中,這些信息如下。
- 傳送的原始位置:data:0;
- 傳送的目的位置:data:0010;
- 傳送的長度:16;
- 傳送的方向:因為正向傳送(每次串傳送指令執行后,si和di遞增)比較方便,所以設置df=0。
好了,明確了這些信息之后,我們來編寫程序:
mov ax,data
mov ds,ax
mov si,0 ;ds:si 指向 data:0
mov es,ax
mov di,16 ;es:di 指向 data:0010
mov cx,16 ;(cx)=16, rep 循環16次
cld ;設置df=0,正向傳送
rep movsb
(2)編程,用串傳送指令,將F000H段中的最后16個字符復制到data段中。
data segment
db 16 dup (0)
data ends
我們還是先來看一下應該為串傳送指令提供什么樣的信息。
要傳送的字符串位於F000H段的最后16個單元中,那么它的最后一個字符的位置:F000:FFFF,是顯而易見的。可以將ds:si指向F000H段的最后一個單元,將es:di指向data段中的最后一個單元,然后逆向(即從高地址向低地址)傳送16個字節即可。
- 傳送的原始位置:F000:FFFF;
- 傳送的目的位置:data:000F;
- 傳送的長度:16;
- 傳送的方向:因為逆向傳送(每次串傳送指令執行后,si和di遞減)比較方便,所 以設置df=1。
程序如下。
mov ax,0f000h
mov ds,ax
mov si,0ffffh ;ds:si 指向 f000:ffff
mov ax,data
mov es,ax
mov di,15 ;es:di 指向data:000F
mov cx,16 ;(cx)=16, rep 循環16次
std ;設置df=1,逆向傳送
rep movsb
11.5 pushf和popf、標志寄存器在Debug中的表示
pushf的功能是將標志寄存器的值壓棧,而popf是從棧中彈出數據,送入標志寄存器中。
pushf和popf,為直接訪問標志寄存器提供了一種方法。
在Debug中,標志寄存器是按照有意義的各個標志位單獨表示的。在Debug中,我們可以看到下面的信息。
下面列出Debug對我們已知的標志位的表示。