8086組合語言程式設計(二) 組合語言學習之偽指令
看到一篇講解微機原理或者組合語言蠻詳細的,因此分享給大家!
1、在計算機中數的表示方式
因為計算機中只能儲存二進位制數,所以一般都是通過二進位制直接進行儲存,但是為了方便閱讀和程式設計師的編碼簡單化,就出現了八進位制、十進位制、十六進位制,一般在彙編的學習過程中以二、十、十六進位制為主。
四種資料的表示形式符號是:B(二進位制)、O(八進位制),D(十進位制),H(十六進位制)
二進位制、八進位制、十六進位制轉化為十進位制都是通過數值為乘以權值,然後求和得出;
十進位制轉換為相應的進位制都是通過除以相應的基數取餘後,逆序達到相關的表示法。
2、在計算中通常通過數值的原碼、反碼、補碼來進行表示。
對於無符號數,討論三碼沒有什麼意義;
對於有符號數,一般第一位代表的是符號位,0代表+,1代表—,同時在表示的過程中,因為儲存器的位數可能存在資料為的擴充套件,一般都是擴充套件符號位。
對於正整數而言,原碼=反碼=補碼,符號擴充套件位為0;
對於負整數而言,原碼 按位取反 = 反碼,反碼+1 = 補碼,符號位保證不變。
求負數的補碼一般有兩種方法:
(1)先寫出其絕對值的原碼,然後把符號位也參加在內進行取反,在加1即可。
(2)按照上面給出負數的求補即可。
補碼的相關規則
(a+b)補 = (a)補+(b)補
(a-b)補 = (a)補-(b)補
3、對8086體系結構中暫存器的認識
(1)存在14個16位的暫存器和8個8位暫存器
通用暫存器包括如下幾類
通用暫存器:傳送、暫存資料和接受相關的運算結果。
1、16位資料暫存器,儲存運算元和操作結果,縮短了訪問記憶體的時間和並且不會佔用相關的系統總數
AX(累加器、被除數的低16位和除法結果) = AH + AL(8位)<兩個獨立的暫存器,下面的相同>
BX(基址暫存器) = BH + BL(8位)<用於基址定址,唯一一個作為儲存器指標使用的暫存器>
CX(字串操作、控制迴圈次數) = CH + CL (移位的時候使用,儲存移位的位數)(8位)
DX(32位乘除法,存放被除數的高16位,或者保留餘數,AX保留結果) = DH + DL(8位)
8個八位暫存器
AH 、AL 、BH、 BL、 CH、 CL、 DH、 DL
指標暫存器:儲存某個儲存單元的地址或者是一段儲存單元的起始地址
2、16位指標暫存器
BP(基址指標 base point 堆疊資料區基址的偏移 )
SP(堆疊指標 stack pointer <push pop 指令的時候使用,儲存棧頂地址>)
上面兩個指標一般是和SS合用
3、16位變值暫存器<一般在字串操作的時候用的比較多>
DI (目的地址 destination )
SI(源地址 source)
上面的兩個暫存器一般是和DS、ES合用
控制暫存器
IP(指令指標)<下一條指令的地址,但是不代表是下次將會執行的指令>
在計算機的組成原理中還有PC(程式計數器,始終指向下一條將要執行的指令,有時候PC和IP的內容相同,有時候又不同,個人理解?)
FLAG(標誌暫存器),其中包含了9個標誌,主要反映儲存器的狀態和相關的運算狀態
前6個運算結果標誌,後3個控制標誌
0 CF (carry flag ) 進位標誌 反映運算是否產生進位或者借位<加法和減法>
2 PF (parity flag ) 奇偶標誌 反映運算結果中1的個數是奇數還是偶數(偶數則置為1)
4 AF(assist flag ) 輔助標誌 在位元組操作中,發生低半位元組向高半位元組進位或借位;在字操作中,低半字向高半字進位或者借位
6 ZF(zero flag ) 零標誌 反映運算結果是否為0
7 SF(signed flag )符號標誌 反映運算結果的符號位,與運算結果的最高位相同
11 OF(over flag) 溢位標誌 反映有符號數加減運算是否引起溢位
8 TF(trace flag ) 跟蹤標誌 置為1後,cpu進入單步方式。主要用於除錯,cpu執行一條指令後被中斷
9 IF(interrupt flag)中斷標誌 決定CPU是否相應外部可遮蔽中斷請求,1則響應,0則不響應
10 DF(direction flag) 方向標誌 決定串操作指令執行時有關指標暫存器調整方向,為1,則串操作指令按減方式改變有關暫存器的值,反之則用加方式
16位段暫存器<定址1M的實體地址空間的時候需要使用它,在計算機的記憶體中程式碼<指令>和資料是放在不同的儲存空間中>
放操作的資料
DS(資料段,一般配合bx作為偏移 data segment)
ES(附加段 extend segment)
放指令
CS(程式碼段,一般和IP連用 code segment)
儲存相關的暫存器值等,放在這裡方便函式返回的時候在恢復現場
SS(堆疊段 stack segment)
2、地址分段和定址
一、明確地址分段的原因
因為在8086中CPU的地址線是20位,那麼實際可用的最大實體地址空間是1MB,但是因為暫存器都是隻有16位和8位之分,最大定址範圍是64KB,為了尋找到所有的實體地址,需要對實體地址空間進行分段。
分段一般是由段首地址+段內偏移地址組成。
但是對於段的首地址不是隨意亂取,通常都以“小段的起始地址為主”
“小段”即是在實體地址中從00000H開始,每16個位元組而劃分的,那麼整個實體地址空間就可以劃分為64K個小段,且首地址的最後四位均為0(用二進位制表示時),所以是16的倍數。
進行分段後,段與段之間就會有重疊、相鄰、不會相干的現象產生。
一般實體地址= 段首地址*16+段內偏移地址。
前者為實體地址,後者斷首地址:偏移地址為邏輯地址。所以一個實體地址可能對應多個邏輯地址的表示。
二、定址方式
(1)彙編程式碼是由兩部分組成:操作碼+運算元
一般操作碼在相應的機器指令體系中有相關的表示,但是運算元的儲存就會不同了。
運算元儲存在如下地方:
一、直接在彙編程式碼中:那麼這種定址方式就是立即數定址 mov ax, 1234H
二、存放在暫存器中:那麼這種定址方式就是暫存器定址 mov ax,bx
三、存放在記憶體中,那麼這種定址方式就比較多了
定址方式:(以源運算元的定址為例)
1、立即數定址 mov ax,1234H
2、暫存器定址 mov ax,bx
3、直接定址 mov ax,【1234H】 (ax) = (ds*16+1234H)
4、暫存器間接定址
mov ax,【bx】 (ax) = (ds*16+bx)
mov ax,【BP】 (ax)=(ss*16+bp)
因為bp的預設是通過ss來定址,不過也可以通過段地址字首來進行強加了
mov ax,ds:[BP] (ax) = (ds*16+bp)
5、暫存器相對定址
mov ax,[bx+1234H] (ax) = (ds*16+bx+1234H)
也可以表示為
mov ax,1234H【bx】
同4,也存在這樣的關係
6、基址變址定址
mov ax,【bx+si】 (ax)= (ds*16+bx+si) <bx是基址暫存器,預設是和ds合用>
也可以表示為
mov ax,【bx】【si】或者是mov ax,【si】【bx】
7、相對基址變址定址
mov ax,【bx+si+1234H】 (ax)=(ds*16+bx+si+1234H)
因為對於其中的很多暫存器都是可以變換的,所以不在這裡一一列舉
但是對於以上7中定址方式到底應該在什麼情況下進行使用還需要進一步的學習。
彙編程式碼是由兩部分組成:操作碼(mov)+運算元,既然有運算元的參與,那麼對於運算元必然需要儲存。
在計算機中,對於運算元的存取至少有兩種方式:暫存器和儲存器,那麼相對而言就產生了各種尋找運算元的方式,本文一一介紹
1、立即定址方式
運算元就包含在指令程式碼中,它作為指令的一部分跟在操作碼後放在程式碼段(CS)中。
這種運算元被稱作立即數,立即數可以是8位的也可以是16位的。
如果立即數是16位的就按照“高高低低”的原則儲存<主要是針對暫存器,與儲存單元的大端和小端沒有關係>
指令示例:mov ax,1234H<H代表的是16位>
AH = 12, AL=34
但是彙編指令在程式碼段中的儲存方式需要依據儲存器的大小端格式來定。
2、暫存器定址方式
運算元放在CPU內部的暫存器中,指令指定暫存器的編號。
對於16位的運算元,暫存器可以如下:
資料暫存器(AX/BX/CX/DX)
地址暫存器(DI/SI)
指標暫存器(SP/BP)
對於8位的運算元,可以是第一節的8個8位的暫存器
因為運算元在暫存器中,不需要佔有系統匯流排,訪問速度快。
指令示例:mov ax,bx
如果執行前:(AX) = 3064H,(BX)=1234H,指令執行後
(AX)= 1234H
3、直接定址方式
指令直接包含運算元的有效地址(偏移地址)
運算元的有效地址一般儲存在程式碼段中,運算元放在資料段中,預設的是ds段
所以運算元的地址由DS加上偏移地址得到有效地址,取出有效地址中的資料進行操作。
指令示例:
MOV AX,[1234H]
假設DS = 4567H,記憶體中(468A4H) = 0001H,那麼有效地址 = (4567H)*16+(1234H) = (468A4H)
那麼暫存器AX = 0001H
因為預設的是DS暫存器,其實也可以指定字首暫存器,即所謂的段超越字首
MOV AX, SS:[1234]H
AX = SS*16+(1234H)
4、暫存器間接定址方式
運算元在暫存器中,運算元的有效地址在SI\DI\BX(DS), BP(SS)
在這四個暫存器之一種,一般情況下如果有效地址在SI、DI、 BX中,則預設段地址是DS
如果在BP中,則預設是SS
不過和上面一樣,指令中也可以指定段超越字首來取得其他段中的資料
指令示例:
MOV AX,[SI] 引用的段暫存器是DS
MOV AX,ES:[SI],引用的段暫存器是ES
MOV [BP],AX,引用的段暫存器是SS
5、暫存器相對定址方式
運算元放在儲存器中,運算元的有效地址是一個基址暫存器(BX、BP)或者是變址暫存器的(SI 、DI)內容加上指令中給定的8位
或者是16位的位移偏移量之和
其中和上面的一樣,引用段地址的時候,BX、SI、DI的段地址暫存器是DS,BP的是SS,不過也可以採用段地址超字首。
其中給定的8位或者是16位採用補碼形式表示。如果偏移量是8位,則需要進行符號擴充套件到16位
MOV AX,[BX+1234H] ,引用段暫存器是DS,有效實體地址=DS*16+BX+1234H,(AX)=(DS*16+BX+1234H)
MOV [BP+1234H],AX,引用段暫存器是SS
MOV ES:[SI+1234H],AX,引用段暫存器是ES
有時候指令也可以表示如左:MOV AX,1234H[BX]等。
6、基址加變址定址方式
運算元在儲存器中,運算元的有效地址由基址暫存器(BX、BP)的內容與變址暫存器(DI、SI)之一的內容相加
如果有BP則段暫存器是SS,其他的是DS
指令示例:
MOV AX,[BX][DI] 等價於 MOV AX,[BX+DI]
有效地址 = DS*16+BX+DI ,(AX) =(有效地址)
同時也允許段超越字首
MOV DS:[BP+SI],AX
定址方式適合於陣列操作,利用基址儲存陣列的首地址,變址儲存元素的相對地址
7、相對基址加變址定址方式
運算元在儲存器中,運算元的有效地址由基址暫存器之一的內容+變址暫存器之一的內容+8位或者是16位的偏移量之和。
段暫存器和相關的符號擴充套件同前面。
如果得到的有效地址超過FFFFH時,取64K的模。
大多數指令既可以處理字資料,也可以處理位元組資料。
算術運算和邏輯運算不侷限於暫存器,儲存器運算元也可以直接參加算術邏輯運算。
指令系統分為如下六個功能組:
(1)資料傳送
(2)算術運算
(3)邏輯運算
(4)串操作
(5)程式控制
(6)處理器控制
指令的一般格式分為四個部分
[標號:] 指令助記符 [運算元1][,運算元2][;註釋]
指令是否帶有運算元完全取決於指令
標號的使用取決於程式的需要,但是不被彙編程式識別,與指令系統無關。
標號有點類似於C語言中的goto語句中的標號,做為一個偏移。
指令助記符代表操作碼,從二進位制的操作碼到助記符的一個翻譯過程。
功能組一:資料傳送指令
1、傳送指令格式:
MOV DST(目的運算元),SRC(源運算元)
源運算元:累加器(AX),暫存器,儲存單元和立即數
目的運算元:不能是立即數
操作後不改變源運算元。
(1)CPU內部之間的傳送(兩個都是暫存器)
MOV AH,AL
MOV DL,DH
MOV BP,SP
MOV AX,CS
源運算元和目的運算元兩個不能都是段暫存器
程式碼段(CS)不能作為目的運算元
指令指標(IP)既不能作為源運算元也不能作為目的運算元
(2)立即數送通用暫存器和儲存單元
MOV AL,3
MOV SI,-3
MOV VARB,-1;VARB是變數名,代表一個儲存單元
MOV VARW,3456H;VARW是一個字變數
MOV [SI],6543H
主要:立即數不能直接傳送給段暫存器
立即數不能是目的運算元
(3)暫存器與儲存器之間的資料傳送
MOV AX,VARW ;VARW是一個字變數,為直接定址
MOV BH,[DI];為暫存器間接定址
MOV DI,ES:[SI+3];為暫存器相對定址,段超越字首
MOV VARB,DL;為暫存器直接定址
MOV DS:[BP],DI;暫存器基址變址定址
MOV VARW,DS;暫存器直接定址
MOV ES,VARW;直接定址
注意:
源運算元和目的運算元型別一樣(byte和word等),除了串操作外
不能同時是儲存器運算元,兩個運算元必須有一個暫存器除立即定址以外。
如果需要在兩個儲存單元中進行資料傳送,可以利用一個暫存器過渡
MOV AX,VARW1
MOV VARW2,AX
實現了VARW1->VARW2的資料傳送。
運算元不能同時為段暫存器,那麼同上也可以進行過渡。
MOV BX,OFFSET TABLE
把TABLE的偏移地址送到BX暫存器中,其中OFFSET為屬性操作符。
傳送指令不影響FLAG暫存器
2、交換指令
利用交換指令可以方便的實現通用暫存器之間或者是與儲存器之間的資料交換
指令格式:
XCHAG OPRD1,OPRD2
此指令把運算元OPRD1與OPRD2的內容進行交換必須保證資料型別的一致。
通過上面的分析,操作指令中必須有一個暫存器,並且儲存器之間,段暫存器之間不能直接通過MOV進行操作。
例如:
XCHAG AL,AH
XCHAG SI,BX
OPRD可是通用暫存器和儲存單元,但是不能包括段暫存器<一定要通過通用暫存器來交換>
還不能有立即數,可以採用各種暫存器和儲存器的定址方式。
指令示例:
XCHAG BX,[BP+SI]; 基址加變址定址方式,基址暫存器和儲存器的資料呼喚[SS]
此指令不影響FLAG
3、地址傳送指令
(1)指令LEA( load effective Address)
傳送有效地址指令,格式如下:
LEA REG,OPRD
該指令把運算元OPRD的有效地址傳送到運算元REG中。
運算元OPRD必須是一個儲存器運算元
運算元REG必須是一個16位的通用暫存器(AX BX CX DX BP SP DI SI)
操作的結果是把偏移地址送給REG,記住不是實體地址,是偏移地址
指令示例:
LEA AX, BUFFER [AX]=BUFFER
LEA DS,[BS+SI] [DS] = BS+SI
LEA SI,[BP+DI+4] [SI] = BP+DI+4
(2)LDS( Load pointer into DS)
段值和段內偏移構成32位的地址指標。
該指令傳送32為地址指標,其格式如下:
LDS REG, OPRD
執行操作: (REG) <- (SRC)
(DS) <- (SRC+2)
該指令把運算元OPRD作為基址所含的一個32位的記憶體中的內容前兩個位元組送到REG中,後兩個位元組送到資料段暫存器DS
運算元OPRD必須是一個32為的儲存器運算元,
運算元REG可以是一個16位的通用暫存器,但實際使用的往往是變址暫存器或者是指標暫存器。
(3)LES(Load pointer into ES)
操作和上面的完全相同。
4、堆疊指令
在8086/8088系統中,堆疊實際是一段隨機訪問RAM區域。
稱為棧底的一端地址較大,稱為棧頂的一端地址較小。
堆疊的段值在堆疊暫存器SS中
堆疊的指標暫存器SP始終指向棧頂
堆疊是以“後進先出”方式工作
堆疊的存取必須以字為單位(16bit = 2Btye)
堆疊的指令分為如下兩種:
(1)進棧指令PUSH
格式如下:PUSH SRC(源運算元)
該指令把源運算元SRC壓棧。
執行過程是:先把棧頂指標SP值減2,SP = SP-2
再把SRC中的值放入SP所指的棧頂中即 [SS*16+SP] = [SRC]
SRC可以是通用暫存器和段暫存器,也可以是字儲存單元
(2)出棧指令POP
格式如下:POP DST(目的運算元)
該指令把棧頂的元素放到DST中,然後把SP加2
執行過程如下:先把堆疊指標SP指的資料放到DST中,【DST】=【SS*16+SP】
再使SP = SP + 2
DST可以是通用暫存器和段暫存器(但是CS除外),也可以是字儲存單元。
注意:
(1)上面兩條指令PUSH和POP只能是字操作
(2)可以使用除立即定址外的其他任何方式
(3)POP指令不允許使用CS暫存器
此兩條指令不影響FLAG標誌位
利用這兩條指令可以是實現兩個段暫存器的資料交換
例如:實現DS、ES的資料交換
PUSH DS
PUSH ES
POP DS
POP ES
在彙編的過程中,堆疊操一般實現“現場儲存”和“現場恢復”,作為引數的傳遞緩衝區等。
彙總:
資料交換有三種方式:
傳送指令、交換指令、堆疊指令
舉例:交換DS、AX的資料
利用傳送指令
MOV BX,AX
MOV AX,DS
MOV DS,BX
利用交換指令
XCHG AX,DS<不能同時是段暫存器>
利用堆疊操作指令如上面的示例。
5、標誌操作指令
(1)標誌傳送指令
1、LAHF(Load AH Flags)
把FLAG暫存器的低八位送到AH,即把CF PF AF ZF SF送到AH中。
不影響標誌暫存器。
2、SAHF(Store AH into Flags)
把AH暫存器的八位傳送到FLAG暫存器的低八位中,剛好和上面的指令作用相反。
影響標誌暫存器。但是不影響8-15中的標誌位。
3、PUSHF和POPF
把FLAG的標誌暫存器壓入和壓出。
可以通過他們的操作來改變FLAG中的標誌位的值。主要可以改變TF標誌。
1、加法指令ADD
格式:ADD OPRD1,OPRD2
(OPRD1) = (OPRD1)+(OPRD2)
例如:MOV AX,7896H; AX=7896H
即AH = 78H, AL=96H;各個標誌寄存位保持不變
ADD AL,AH ;AL=0EH,AH = 78H,即AX = 780EH(0111100000001110)
此時如果FLAG暫存器的值分別為
CF = 1, ZF = 0,SF = 0,OF = 0,AF = 0,PF = 0
繼續操作如下:
ADD DX,0F0F0H
執行前(DX) = 4652H,執行後(DX)=3742H,ZF=0,SF=0,CF=1,OF=0
如果執行如下操作:
ADD AX,4321H
執行前(AX)=62A0H,執行後(AX)=A5C1H SF=1,ZF=0,CF=0,OF=1
講解一下執行的過程,為什麼FLAG標誌位的狀態發生了變化。
在暫存器中一般數值都是用補碼錶示,最高位代表符號位。
但是在加法指令中,是不區分運算元的符號位的,因為補碼的表示完全避開了這個符號位的概念<在下一篇目錄中會有說明>,符號的概念只在程式語言級別才有區分(針對加法和減法)
根據上面的分析可以知道:加法指令影響標誌位。
PF標誌位表示結果包含的1的個數,如果為偶數個則為1,如果是奇數個則為0
2、帶進位的加法指令ADC( ADD with carry )
格式如下:
ADC,OPRD1,OPRD2
OPRD1 = OPRD1+OPRD2+CF
例如:下列指令序列執行了兩個雙精度的加法。(雙精度32位)
設目的運算元放在DX和AX暫存器中,其中DX存放高位字,AX存放低位字
源運算元放在BX,CX中,其中BX存放高位字
(DX) = 0002H (AX) = 0F365H
(BX) = 0005H (CX) = 0E024H
指令序列如下:
ADD AX,CX
ADC DX,BX
執行第一條指令後:
AX = 0D389H(1101001110001001),SF=1, ZF=0,CF=1,OF=0
執行第二條指令後:
DX = 00008H(0000000000001000),SF=0, ZF=0, CF=0, OF=0
從上面的例子可以看到:
為了實現雙精度數的加法,必須用兩條指令來完成,低位和低位相加,
產生進位,然後再使用ADC。
另外帶符號的雙進度數的溢位,需要根據ADC指令的OF來判斷
ADD指令的OF沒有作用。
通過上面例子可以看出:影響FLAG
3、加1指令INC(INCrement)
加1指令的格式如下:
INC OPRD
OPRD = OPRD + 1;
運算元可以是通用暫存器,也可以是儲存單元
這條指令的執行結果影響標誌位ZF,SF,OF,PF和AF,但它不影響CF
該指令主要用於調整地址指標和計數器。
兩個數相加,如果最高有效位不同,那麼肯定不會發生溢位,即OF=0,但是會有進位,CF的值根據是否有進位來判斷
如果最高有效位相同,相加的結果的最高有效位如果與運算元相反,那麼肯定有溢位了,則OF=1,證明發生了錯誤,CF的值根據是否有進位來判斷
在高階的程式語言層面,這個就作為“截斷”進行處理,然後可以根據OF和CF的值來判斷結果是錯誤,還是正確。
如果OF = 1,則結果肯定是錯誤,不用理會CF
如果OF = 0,則結果可能是正確的,如果CF=0,則正確,如果CF=1,則發生了進位,結果被“截斷”
4、減法指令SUB(SUBtraction)
格式如下:
SUB OPRD1,OPRD2
執行的操作:(OPRD1) = (OPRD1)-(OPRD2)
例如:
SUB [SI+14H],0136H
指令執行前 (DS) = 3000H, (SI)=0040H
(300054H) = 4336H
指令執行後 (30054H) = 4200H
SF=0, ZF=0, CF=0, OF=0
例子如下:
SUB DH,[BP+4]
指令執行前
(DH)=41H,(SS)=0000H,(BP)=00E4H,(000E8H)=5AH
指令執行後
(DH)=E1H,(SS)=0000H,(BP)=00E4H,(000E8H)=5AH
SF=1, ZF=0, CF=1<借位>, OF=0
5、帶借位的減法SBB( SuBtrace with Borrow)
與帶借位的加法剛好相反
SUC OPRD1,OPRD2
OPRD1 = OPRD1-OPRD2-CF
例如:
SBB AL,DL
SBB DX,AX
該條指令主要用於多位元組想減的場合。
6、減1指令 DEC(DECrement)
格式如下:
DEC OPRD
(OPRD) = (OPRD)-1
例如:
DEC VARB ;VARB是位元組變數
運算元OPRD可以是通用暫存器,也可以是儲存單元。
減1指令,在相減的時候,把運算元作為一個無符號數對待。
這條指令執行的結果影響ZA,OF,PF,SF,AF但是不影響CF
該條指令主要用於調整地址指標和計數器。
兩個運算元相減,如果最高有效位相同,則不會發生溢位,則OF=0,CF根據是否借位來判斷,如果CF=1,則借位,如果CF=0,則沒有借位。
如果最高有效位不同,且操作的結果和減數的最高有效位相同,則OF=1,CF根據是否借位來判斷,如果CF=1,則借位,如果CF=0,則沒有借位
7、取補指令NEG(NEGate)
格式如下:
NEC OPRD
這條指令對運算元取補,就是用零減去運算元OPRD,在把結果送到OPRD中
(OPRD) = -(OPRD)
運算元可以是通用暫存器,也可以是儲存單元
此指令的執行結果影響CF/ZF/SF/OF/AF和PF
運算元為0時,求補的運算結果是CF=0,其它情況則均為1,都是借位操作
如果在位元組操作的時候對-128取補,或在字操作的時候對-32768取補,則運算元不變,但是OF被置為1,
其它都是0
8、比較指令CMP(CoMPare)
格式如下:
CMP OPRD1,OPRD2
這條指令完成運算元OPRD1減去OPRD2,運算結果不送到OPRD1,
但是影響標誌CF/ZF/SF/OF/AF和PF
記住雙運算元中至少有一個暫存器。
比較指令主要用於比較兩個數的關係,是否相等,誰大誰小
執行了比較指令後,可以根據ZF是否置位,來判斷兩者是否相等
如果兩者都是無符號數,則可以根據CF判斷大小,如果借位了則前者小
如果兩個都是有符號數,則可以根據SF和OF判斷大小,
若為無符號數,則根據CF判斷大小:
若CF = 1,則OPRD1 < OPRD2
若CF = 0,則OPRD1 >= OPRD2,如果ZF不等於1則是大於
若為有符號數,則根據SF和OF判斷大小:
若SF = 1, OF = 1,則OPRD1 > OPRD2,說明發生了溢位,相減後為負數1(正數-負數)
若SF = 1, OF = 0,則OPRD1 < OPRD2,說明沒有發生溢位,相減後為負數
若SF = 0, OF = 1,則OPRD1 < OPRD2,說明發生了溢位,相減後為正數,但是發生了溢位(負數-正數)
若SF = 0, OF = 0,則OPRD1 > OPRD2,說明正常操作,且結果為正數
在彙編指令中,是不區分有符號數和無符號數,但是彙編指令中對於加減法指令是不區分有符號數和無符號數的。
但是乘除法指令是區分有符號數和無符號數。
一、只有一個標準!
在組合語言層面,宣告變數的時候,沒有 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。
三、可愛又可怕的c語言。
為什麼又扯到 c 了?因為大多數遇到有符號還是無符號問題的朋友,都是c裡面的 signed 和 unsigned 宣告引起的,那為什麼開頭是從彙編講起呢?因為我們現在用的c編譯器,無論gcc 也好,vc6 的cl 也好,都是將c語言程式碼編譯成組合語言程式碼,然後再用匯編器彙編成機器碼的。搞清楚了彙編,就相當於從根本上明白了c,而且,用機器的思維去考慮問題,必須用匯編。(我一般遇到什麼奇怪的c語言的問題都是把它編譯成彙編來看。)
C 是可愛的,因為c符合kiss 原則,對機器的抽象程度剛剛好,讓我們即提高了思維層面(比彙編的機器層面人性化多了),又不至於離機器太遠 (像c# ,Java之類就太遠了)。當初K&R 版的c就是高階一點的彙編……:-)
C又是可怕的,因為它把機器層面的所有的東西都反應了出來,像這個有沒有符號的問題就是一例(java就不存在這個問題,因為它被設計成所有的整數都是有符號的)。為了說明c的可怕特舉一例:
#include <stdio.h>
#include <string.h>
int main()
{
int x = 2;
char * str = "abcd";
int y = (x - strlen(str) ) / 2;
printf("%d\n",y);
}
結果應該是 -1 但是卻得到:2147483647 。為什麼?因為strlen的返回值,型別是size_t,也就是unsigned int ,與 int 混合計算時型別被自動轉換了,結果自然出乎意料。。。
觀察編譯後的程式碼,除法指令為 div ,意味無符號除法。
解決辦法就是強制轉換,變成 int y = (int)(x - strlen(str) ) / 2; 強制向有符號方向轉換(編譯器預設正好相反),這樣一來,除法指令編譯成 idiv 了。我們知道,就是同樣狀態的兩個記憶體單位,用有符號處理指令 imul ,idiv 等得到的結果,與用 無符號處理指令mul,div等得到的結果,是截然不同的!所以牽扯到有符號無符號計算的問題,特別是存在討厭的自動轉換時,要倍加小心!(這裡自動轉換時,無論gcc還是cl都不提示!!!)
為了避免這些錯誤,建議,凡是在運算的時候,確保你的變數都是 signed 的。(完)
在前面一節談到,在彙編中對於加法和減法指令是沒有所謂的有符號數加法和有符號數減法,統一通過補碼進行運算,然後再根據標識暫存器的相關標識位進行判斷,來辨別運算結果是否正確,主要是以OF,SF和CF的對比來判斷。
但是乘法指令和除法指令區分了相關的有符號操作和無符號操作,因為在運算的結果中需要進行符號位的擴充套件。
乘法指令和除法指令都分為位元組和字的操作,如果是兩個8bit位的運算元,則結果存放在AX中,如果是兩個16bit的運算元,則運算元和結果放在AX和DX中。
(1)無符號數的乘法指令
指令格式如下:
MUL OPRD
如果OPRD是8bit位的無符號數,那麼有一個隱藏數在AL中,最後的結果放在AX中。
如果OPRD是16bit位的無符號數,那麼有一個隱藏數在AX中,最後的結果是低字放在AX中,高字放在DX中。
對於標誌為的影響比較特殊:
如果高半部分不為0,則CF = 1,OF=1(說明操作結果有效),如果為0則CF=OF=0,說明CF和OF是對高半部分進行記錄,但是對其它FLAG位的影響沒有定義。
(2)有符號數的乘法指令
格式如下:
IMUL OPRD
如果OPRD是8bit位的無符號數,那麼有一個隱藏數在AL中,最後的結果放在AX中。
如果OPRD是16bit位的無符號數,那麼有一個隱藏數在AX中,最後的結果是低字放在AX中,高字放在DX中。
對於標誌為的影響比較特殊:
如果高半部分是低半部分的符號擴充套件,則CF =OF=0(說明操作結果有效),如果不是符號擴充套件則CF=OF=1,說明CF和OF是對高半部分進行記錄,但是對其它FLAG位的影響沒有定義。
乘法指令適應於我們前面介紹的所有定址方式。
(3)無符號的除法指令
格式如下:
DIV OPRD
如果OPRD是8bit位的無符號數,那麼有一個隱藏數在AX中,最後的結果AL儲存商,AH儲存餘數。
如果OPRD是16bit位的無符號數,那麼有隱藏數在AX、Dx中,其中AX儲存運算元的低字,DX儲存運算元的高字,最後的結果是商放在AX中,餘數放在DX中。
除法指令是狀態標誌沒有意義,結果可能產生溢位,溢位時8086CPU中就產生編號為0的內部中斷.實用中應該考慮這
個問題.
(4)有符號數的除法指令
指令格式如下:
IDIV OPRD
整個的操作過程和DIV一樣,沒有什麼比較特殊的地方
彙編指令中的移位操作分為算術移位和邏輯移位
一般在進行左移操作的時候,算術移位和邏輯移位的處理過程都比較簡單:移除左邊的最高位,最低位補零
但是在進行右移操作的時候,算術移位移除右邊的數字然後左邊的最高位進行符號擴充套件,不過邏輯移位就是補零,則個需要注意一點。
對於需要進行左移和右移的操作,一般都是需要指定移動位數M,如果M=1則可以直接以立即數給出,如果移位超過1則需要把移位放在CL中。
移位操作主要分為如下幾個指令:
SAL OPRD,M 算術左移
SHL OPRD,M 邏輯左移
SAR OPRD,M 算術右移
SHR OPRD,M 邏輯右移
迴圈移位沒有符號位的擴充套件等性質
ROL OPRD,M 迴圈左移<如果運算元為Nbit位,則移動N次後可以還原>
ROR OPRD,M 迴圈右移
RCL OPRD,M 帶進位的迴圈左移<CF作為迴圈移動的一部分,需要移動N+1次才可以復位>
RCR OPRD,M 帶進位的迴圈右移
一般移位操作都是和邏輯運算結合進行運算元的結合與分解運算
右移操作一般是把最高位移動到CF中
帶進位的迴圈移位操作也是對CF進行了操作,對其他標誌位的影響根據相關性質來決定。
在彙編指令中跳轉指令分為兩種,一種是無條件跳轉指令,一種是有條件跳轉指令。
對於前者無條件跳轉指令有點類似於高階語言C中的goto語句,goto標誌符,無跳轉指令的格式也是類似JMP 標號;
對於有條件跳轉指令通常都是根據FLAG暫存器的相關狀態值SF,OF,AF,PF,CF是否被設定為1或者是0來進行跳轉的選擇,這個就可以實現相關的分支語句。類似於高階語言中的if等。
(1)無條件跳轉指令JMP
基本格式如下:
JMP 標號;
因為在作業系統中我們一般對程式進行分段處理,那麼在不同的段就會設定不同的CS暫存器,執行不同指令的過程中實質是設定CS與IP暫存器的值,然後CPU以此來進行指令的取出,由此對於跳轉指令我們就分為段內跳轉和段間跳轉,前者是在一個程式碼段中,後者是實現不同的程式碼段的跳轉。
首先說一下段間無條件跳轉。
段間的無條件跳轉的實現原理是:彙編器根據JMP後面設定的標號,計算出標號對應的段內偏移與此時IP暫存器中的值得差值,然後讓IP加上該差值,實質就是設定IP的值為該標號對應的段內偏移值。
根據差值所佔位的大小我們又分為:無條件段內近轉移和無條件段內短轉移。
對於前者,偏移與IP的差值大小隻佔2個位元組,後者佔1個位元組
指令格式分別如下:
無條件段內短轉移:JMP SHORT 標號
無條件段內近轉移:JMP NEAR PTR 標號
對於這個PTR什麼時候需要新增我也不是很清楚,到後面的學習過程中明瞭後在進行修改。
對於無條件轉移指令,此時的IP值是通過標號直接設定的,在彙編器解析的時候進行設定,但是有時候我們可以把需要設定的IP值放到通用暫存器或者是儲存器中,那麼這樣就可以實現無條件段內間接跳轉指令。
指令的格式如下:
JMP OPRD
其中OPRD可以是通用暫存器,也可是儲存單元,定址方式除了是立即數定址外,可以是其它的定址方式。
例如:
JMP AX;JMP [AX];JMP WORD PTR [1234H]等
(2)無條件段間跳轉
所謂的無條件段間跳轉就是通過相關的操作直接設定CS和IP暫存器的值,使得執行不同程式碼段中的程式碼
指令格式和上面的大同小異,但是彙編器在進行解析的時候會設定CS和IP的值。
指令分為兩種:一種是直接跳轉
JMP FAR PTR 標號
一種是間接跳轉,通過直接定址的方式,把儲存器中的低字放到IP中,高字放到CS中,指令格式如下
JMP DWORD PTR OPRD
例如:JMP DWORD PTR [1234H],則執行後IP = DS*16+[1234H],CS = DS*16+[1236H]
總結了一些JMP跳轉指令的相關格式:
格式 |
描述 |
舉例 |
類別 |
說明 |
jmp 16位暫存器 |
以16位暫存器的值改變IP |
jmp ax |
段內轉移 |
|
jmp 段地址:偏移地址 |
以立即數改變段地址和偏移地址 |
jmp 0045H:0020H |
段間轉移 |
|
jmp short 標號 |
以標號地址後第一個位元組的地址來改變IP,實際上這個功能可以作如下描述: |
jmp short sign |
段內短轉移 |
對IP的修改範圍是-128->127,實際演算法是編譯器根據當前IP指標的指向來計算到底偏移多少個位元組來指向下一條指令,下面這段程式碼就會出編譯錯誤 |
jmp near ptr 標號 |
以標號地址後第一個字的地址來改變IP, |
jmp near ptr sign |
段內近轉移 |
對IP的修改範圍是-32768->32767 |
jmp far ptr標號 |
以標號的段地址和指令地址同時改變CS和IP |
jmp far ptr sign |
段間轉移 |
|
jmp word ptr 記憶體地址 |
以記憶體地址單元處的字修改IP,記憶體單元可以以任何合法的方式給出 |
jmp word ptr ds:[si] |
段內轉移 |
|
jmp dword ptr記憶體地址 |
以記憶體地址單元處的雙字來修改指令,高地址內容修改CS,低地址內容修改IP,記憶體地址可以以任何合法的方式給出 |
jmp dword ptr [bx] |
段間轉移 |
s1 segment |
對於JMP 段地址:偏移地址,並不是所有的MASM都支援的,需要依據實際的形式來判斷。
上面我們看到了段內的無條件跳轉指令,但是和很多高階語言進行對比,我們在很多時候都是通過條件的判斷來決定是否需要進行跳轉,同樣在彙編指令中也提供了相關的條件跳轉指令,我們現在一一進行介紹:
明確一下,在彙編指令中N代表的是否。同時進行條件跳轉的指令都是段內跳轉,因此有短和近跳轉了撒!
(1)根據標識FLAG暫存器來判斷是否需要進行跳轉
我們根據前面的需要知道相關的算術運算、邏輯運算、移位運算(部分指令會影響CF)都會影響FLAG暫存器中的部分標識位的值,那麼根據這些標誌位我們可以判斷是否需要進行跳轉,譬如CMP AX,BX是判斷AX和BX的大小,那麼通過相關設定的標誌位我們就可以判斷是AX大還是BX大,然後決定是否需要轉移嘛,這就是所謂的分支語句了撒!
對於有符號數分大於(G-great),等於(E-equal),小於(L-light)
對於無符號數分為大於(A),等於(E),小於(B)
根據標誌位跳轉的指令:
JE ;等於則跳轉 JNE ;不等於則跳轉 JZ ;為 0 則跳轉 (ZF) JNZ ;不為 0 則跳轉 JS ;為負則跳轉 (SF) JNS ;不為負則跳轉 JC ;進位則跳轉 (CF) JNC ;不進位則跳轉 JO ;溢位則跳轉(OF) JNO ;不溢位則跳轉 JA ;無符號大於則跳轉 JNA ;無符號不大於則跳轉 JAE ;無符號大於等於則跳轉 JNAE ;無符號不大於等於則跳轉 JG ;有符號大於則跳轉 JNG ;有符號不大於則跳轉 JGE ;有符號大於等於則跳轉 JNGE ;有符號不大於等於則跳轉 JB ;無符號小於則跳轉 JNB ;無符號不小於則跳轉 JBE ;無符號小於等於則跳轉 JNBE ;無符號不小於等於則跳轉 JL ;有符號小於則跳轉 JNL ;有符號不小於則跳轉 JLE ;有符號小於等於則跳轉 JNLE ;有符號不小於等於則跳轉 JP ;奇偶位置位則跳轉 (PF) JNP ;奇偶位清除則跳轉 JPE ;奇偶位相等則跳轉 JPO ;奇偶位不等則跳轉跳轉相關的標誌位:
11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF | |||
溢 出 |
符 號 |
零 | 未 用 |
輔 助 |
未 用 |
奇 偶 |
未 用 |
進 位 |
通過上面的跳轉指令我們就可以實現簡單的分支和迴圈,例如
MOV CX,10H
NEXT:
........
DEC CX
JNZ NEXT
實現的是執行NEXT中的程式碼段10次
但是通過自己手動的寫相關的迴圈語句有時候很複雜,增加了編碼的難度,因此在彙編指令中有了如下的專門的迴圈指令,如下所示:
LOOP = CX不為零的時候進行跳轉
LOOPE/LOOPZ = CX不為零並且相等的時候跳轉
LOOPNE/LOOPNZ = CX不為零並且不相等的時候跳轉
LCXZ CX為零的時候跳轉
通過相關的迴圈+跳轉語句就可以實現高階語言中的分支語句和迴圈語句的執行了
程式設計語言是實現人機交換資訊(對話)的最基本工具,可分為機器語言、組合語言和高階語言。本章重佔介紹組合語言。
(1)組合語言是一種面向機器的程式設計語言,其基本內容是機器語言的符合化描述;
(2)通常組合語言的執行語句與機器語言的執行指令是一一對應的;
(3)組合語言允許程式直接使用暫存器,標誌等微處理器晶片內部的特性;
(4)同高級語言程式相比,與其等效的組合語言執行速度要塊,目的碼所佔的記憶體要少;
(5)組合語言是系統軟體和實時控制系統程式設計師必須掌握的。
1.機器語言
機器語言用二進位制編碼表示每條指令,它是計算機能只別和執行的語言。用機器語言編寫的程式稱為機器語言程式或指令程式(機器碼程式)。因為機器只能直接識別和執行這種機器碼程式,所以又稱它為目標程式。顯然,用機器語言縮寫程式不易記憶、不易查錯與不易修改。為了克服機器語言的上述缺點,可採用有一定含義的符號即指令助記符來表示指令。一般都採用某些有關的英文單詞的縮寫,這樣就出現了另一種程式語言――組合語言。
2.組合語言
組合語言是用指令的助記符、符號地址、標號等來表示指令的程式語言,簡稱符號語言。它的特點是易讀、易寫、易記。
它與機器語言指令是一一對應的。組合語言不像高階語言(如BASIC)那樣通用性強,而是性某種計算機所獨有,與計算機的內部硬體結構密切相關。用匯編語言縮寫的程式叫組合語言程式。
把組合語言源程式翻譯成目標程式的過程稱為彙編過程,簡稱彙編。完成這個任務有兩種方法:
①手工彙編。所謂手工彙編是程式設計人員根據機器語言指令與組合語言指令對照表,把編好的組合語言程式翻譯成目標程式。
組合語言程式 機器語言程式
MOV AL,0AH B0H 0AH
ADD AL,14H 04H 14H
②機器彙編。所謂機器彙編就是由彙編程式自動將使用者編寫的組合語言源程式翻譯成目標程式。
這裡,彙編程式是由廠家為計算機配置的擔任把彙編源程式成目標程式的一種系統軟體。
以上兩種程式語言都是低階語言。儘管組合語言具有執行速度快和易於實現對硬體的控制等優點,但它仍存在著機器語言的某些缺點:與CPU的硬體結構緊密相關,不同的CPU其組合語言是不同的,這使得組合語言程式不能移植,使用不便;其次,要用匯編語言進行程式設計,必須瞭解所使用的CPU硬體的結構與效能