本篇文章是在學習王爽老師的彙編語言第三版時寫的筆記,並非很全面。編程
說到彙編咱們先來講一下機器語言,機器語言是機器指令的集合,機器指令就是一臺機器能夠正確執行的命令,在計算機中就是一列二進制數字,計算機將其轉變爲一系列高低電平使計算機的電子器件受到驅動進行運算,可是一列二進制數字難於辨別和記憶,因而彙編語言產生了,他將難於記憶的機器指令轉化成與人類語言香接近的彙編指令,使其便於閱讀和理解,每一種CPU都擁有本身的彙編指令集。數組
彙編語言有三類指令組成:框架
CPU是控制計算機運做的核心部件,可是想讓一個計算機工做必需要向他提供指令和數據,指令和數據是存放在存儲器中的,也就是咱們平時所說的內存。
存儲器劃分紅許多存儲單元,每一個存儲單元都有一個編號,也就是地址,當CPU想要訪問數據的時候,它須要先找到這個數據所在的地址,而後再讀取數據,同時還須要知道使用什麼器件操做。
由此引出CPU進行數據的讀寫時須要的三個條件:oop
用於肯定數據存儲地址也就是數據存放在哪一個存儲單元(存儲單元以字節爲最小單位),假設一個CPU有N根地址線,那麼能夠說這個CPU的地址總線的寬度爲N,CPU能夠尋找的存儲單元個數爲2的N次方個,內存地址的大小受到地址線的約束。學習
用於CPU與其餘器件之間的數據傳送,8根數據線一次只能傳送8位二進制數,也就是一個字節。大數據
用於CPU對外部器件的控制,控制總線的寬帶決定了CPU對其餘器件的控制能力。3d
CPU是由運算器(信息處理)、控制器(控制器件工做)、寄存器(信息存儲)等器件組成,他們之間經過總線相連。指針
通用寄存器時用於存放通常性數據的,以8086 CPU爲例,8086 CPU全部的寄存器都是16位的,8086 CPU中的通用寄存器有AX、BX、CX、DX。爲了兼容上一代CPU中的8位寄存器,這4個寄存器均可以拆成兩個8位的寄存器使用,也就是將16位分紅兩個8位,AX可分爲AH和AL,BX可分爲BH和BL,CX可分爲CH和CL,DX可分爲DH和DL。這裏的H能夠記爲High,高位,L能夠記爲Low,低位。
再來講一下數據寬度,由於寄存器有16位和8位兩種,因此數據寬度能夠分爲字節和字,一個字節由8個bit組成,能夠直接使用低位存儲,一個字由兩個字節,也就是16位,好比一個十六進制3E10,將這個十六進制存放到AX寄存器總,3E就存放在高位AH中,10存放在低位AL中。code
以前說過數據是經過地址線存放在內存單元中的,那麼就必需要肯定這個內存單元的地址。
基礎地址=段地址x16
基礎地址+偏移地址=物理地址
因此物理地址=段地址x16+偏移地址,這裏的16是十進制的16,也就是十六進制的A。
這裏的段地址能夠理解爲一段一段的內存存儲單元,可是段地址並非固定的,能夠認爲10000H-100FFH是一個段,也能夠將這段地址當作是10000H-1007F和10080H-100FFH組成的段,以10000H-100FFH爲例,它的段地址是1000H,偏移地址爲FF,這樣基礎地址就是1000Hx10H=10000H,這樣就能夠肯定基礎地址。再加上偏移地址,就能夠肯定這段內存。對象
既然能夠經過段地址和偏移地址肯定物理地址,那麼就須要寄存器來存放段地址,8086 CPU中有4個段地址:CS、DS、SS、ES,偏移地址存放在IP寄存器中,這裏以CS寄存器爲例,能夠簡單的理解爲經過CS(代碼段寄存器)、IP(指令指針寄存器)兩個寄存器能夠肯定CPU須要讀取的指令的地址,也就是CPU會將CS:IP指向的內容看成指令執行。
mov指令
mov指令稱爲傳送指令,可使用這個指令爲寄存器賦值,例:mov ax,123
這個語句就相似於C語言中的賦值操做ax=123;
jmp指令
jmp指令叫作轉移指令,能夠用於設置CS、IP中的值(mov指令不能修改CS、IP中的值),例:jmp 2AE3:3
,執行這個指令後,CS的值被修改成2AE3H,IP的值被修改成0003H,CPU將從2AE33H處讀取指令。
(1)從CS:IP指向的內存單元讀取指令,讀取的指令進入指令緩衝器
(2)IP指向下一條指令
(3)執行讀取進指令緩衝器的指令
(4)重複上述三個步驟
在上一篇中說到一個16位寄存器能夠存放一個字(16位)或者一個字節(8位),當存放一個字節的時候只須要一個內存單元(內存單元是以字節爲單位的,8位),而存放一個字須要兩個內存單元,這樣存放一個字就須要兩個連續的內存單元,這個16位的字,高位存放在高地址,低位存放在低地址。
內存地址 | 內存數據 |
---|---|
0 | 20H |
1 | 4EH |
2 | 12H |
3 | 00H |
對於字來講0就是低地址單元,1是高地址單元,則字型數據4E20H的低地址位20存放在0號單元,高地址位4E存放在高地址單元,由於它的起始地址爲0,又能夠稱做0地址字單元。
經過前面學過的知識咱們能夠知道當CPU想要對一個內存單元進行操做時,必須知道它的地址,要知道內存單元的地址就要知道它的段地址和偏移地址,在8086 CPU中,DS寄存器就是用來存放段地址的,執行指令的時候,CPU會自動讀取DS中的數據爲內存單元的段地址,使用[偏移地址]
來表示偏移地址,假設DS寄存器中此時存放的是1000H,那麼mov al,[0]
就表示將10000H(物理地址=段地址x16+偏移地址)地址上存放的數據存到al中。
若是想要修改DS寄存器中的值,那麼直接使用mov指令將數字存到DS寄存器中是不行的,只能先將值存到一個寄存器中,再使用mov指令將這個寄存器中的值存到DS中,例:
mov bx,1000H mov ds,bx mov ax,[0]
這樣就將地址爲10000H處的值存放到ax寄存器中了,也就是將下表中的4E20H存放到ax中。(ax是十六位,一個字,兩個字節)
內存地址 | 內存數據 |
---|---|
0 | 20H |
1 | 4EH |
2 | 12H |
3 | 00H |
因此一個內存單元地址的肯定能夠經過段地址DS+[偏移地址]
進行肯定。
顧名思義,add指令就是用來作加法操做的,sub指令就是用來作減法操做的,例如add ax,8
這條指令至關於C語言中的ax = ax + 8
,sub ax,8
至關於C語言中的ax = ax - 8
。
add和sub指令能夠操做的對象有如下幾種形式,以add指令爲例:
add 寄存器,數據 例如:add ax,8 add 寄存器,寄存器 例如:add ax,bx add 寄存器,內存單元 例如:add ax,[0] add 內存單元,寄存器 例如:add [0],ax
棧,是一段具備特殊訪問方式的存儲空間,它的存取規則是先進後出,後進先出,就像是一個上面沒有蓋的桶,最後放進去的東西只能最早取出來。
那麼咱們怎麼知道在連續的存儲空間中,哪一段是棧,哪一段不是棧,回想一下,CPU是根據CS、IP兩個寄存器中存放的值判斷當前指令存放的位置,根據DS、偏移地址判斷數據存放在哪一個內存單元,顯然,也會有相應的寄存器用來判斷哪一段是棧,在8086 CPU中,經過段寄存器SS和寄存器SP就能夠肯定棧的位置,棧有棧頂和棧底,棧頂的段地址存放在SS中,SP用於存放偏移地址,在任意時刻SS:SP指向棧頂元素,它指向的第一個元素能夠理解爲棧底,之後每存放一個數據SS:SP就向上提高,而它所指向的就是棧頂。
有了棧,那麼就能夠對棧進行存取數據的操做,使用的指令時push和pop指令,push指令用於入棧操做,也就是存數據,pop指令用於出棧,也就是取數據。8086 CPU的入棧、出棧操做都是以字爲單位。
假設有以下一段連續的內存單元,此時SS爲1000H,SP爲000EH,AX爲1234H,棧頂爲1000EH。
內存地址 | 數據 |
---|---|
1000AH | |
1000BH | |
1000CH | |
1000DH | |
1000EH | |
1000FH |
首先,由於SS爲1000H,SP爲000EH,因此棧頂是指向1000EH位置的:
SS爲1000H,SP爲000EH,AX爲1234H
內存地址 | 數據 |
---|---|
1000AH | |
1000BH | |
1000CH | |
1000DH | |
1000EH | [SS:SP] |
1000FH |
接着咱們執行入棧操做,將AX(1234H)入棧,使用push ax
操做進行壓棧。
SS爲1000H,SP爲000CH,AX爲1234H
內存地址 | 數據 |
---|---|
1000AH | |
1000BH | |
1000CH | 34[SS:SP] |
1000DH | 12 |
1000EH | |
1000FH |
此時,新的棧頂就變成了1000CH,接着,咱們再將一個當即數5678H壓入棧中,使用push 5678
:
SS爲1000H,SP爲000AH,AX爲1234H。
內存地址 | 數據 |
---|---|
1000AH | 78[SS:SP] |
1000BH | 56 |
1000CH | 34 |
1000DH | 12 |
1000EH | |
1000FH |
這是,棧頂就是1000AH,SS爲1000H,SP爲000AH。
演示完了入棧,咱們再進行出棧,使用pop ax
操做,將出棧的數據放入到ax寄存器中:
SS爲1000H,SP爲000CH,AX爲5678H。
內存地址 | 數據 |
---|---|
1000AH | 78 |
1000BH | 56 |
1000CH | 34[SS:SP] |
1000DH | 12 |
1000EH | |
1000FH |
注意,出棧並不意味着以前寫入內存單元的數據被刪除了,以前寫入的數據仍是存在的,只不過它不在棧中了。
想要完整的描述一個內存單元,須要兩個條件:1.內存單元的地址 2.內存單元的長度(類型)。
首先講一下[0]的含義,[0]表示的內存單元,偏移地址爲一個當即數0,段地址默認保存在DS寄存器中(以前的文章中講到過,經過DS寄存器和偏移地址(ds:[0])就能夠肯定內存中的一個地址)。
說完[0]再回頭來講[bx],[bx]表示的依然是偏移地址,可是它不是一個當即數了,而是保存在一個寄存器中,段地址仍是默認保存在DS寄存器中。
loop指令的格式是:loop 標號,CPU執行loop指令的時候,要進行兩部操做,第一步:cx = cx - 1 第二步:判斷cx中的值,不爲零則轉至標號處執行程序,若是爲零則向下執行。從這兩步咱們能夠看出loop指令的執行結果受到cx中值的影響,一般狀況下,咱們使用loop指令來實現循環功能,cx中存放loop指令須要循環的次數。
在彙編語言中,包含兩種指令,一種是彙編指令,另外一種是僞指令,彙編指令是對應着機器碼,能夠直接被編譯爲機器指令被CPU執行,而僞指令不會被CPU執行,它是用來讓編譯器進行相關的編譯工做。
(1)segment
segnment和ends是一對成對使用的僞指令,用於定義一個段,segnment用於聲明一個段開始,ends用於聲明一個段結束,使用格式爲:
段名 segnment
段名 ends
好比用codesg爲段名聲明一個用於存放數據的段:
codesg segnment
codesg ends
一個彙編程序由多個段造成,這些段用來存放代碼、數據或者看成棧空間使用,一個有意義的彙編程序至少要有一個段用於存放代碼。
(2)end
end指令是一個彙編程序的結束標記,編譯器在執行彙編程序的時候若是碰到end就結束對源程序的編譯。
(3)assume
assume是假設的意思,它用於假設某一個寄存器和程序中的某一段相關。好比:
assume cs:codesg
就是將代碼段codesg和CPU的段寄存器cs聯繫在一塊兒。
再來說講標號,除了彙編指令和僞指令之外,還有標號,一個標號指代一個地址。好比codesg segment,這個codesg最終將被處理爲一個段的段地址。
接着來分析一段求2的10次方彙編程序
assume cs:code code segment mov ax,2 mov cx,9 s: add ax,ax loop s mov ax,4c00h int 21h code ends end
分析:
先來看一下總體框架
assume cs:code表示cs與code代碼段有聯繫
code segment和code ends用於標識一段代碼段
end用於表示程序結束
接着分析中間部分
mov ax,2 將2存到ax寄存器中用做初始值
將9存放到cx中用於循環次數計數
s是標號,指代add ax,ax的地址
add ax,ax就是將ax中的值進行自加
loop s就是執行到這一句的時候就就跳轉到標號爲s的地方,也就是add ax,ax的地址,loop每執行一次cx就減一,只到cx爲0就執行結束,loop指令就再也不跳轉而是接着向下執行
最後的 mov ax,4c00h和 int 21h暫時先無論
這樣就將2的10次方的值保存在ax中了。
(1)and指令
and指令:邏輯與,按位進行與運算,運算規則是有0爲0
例:
mov al,01100011B and al,00111011B
與運算以後的結果是00100011B
有了這條指令後,咱們就能夠將咱們想要設置爲0的位置設置爲0,而其餘的位置不用改變
(2)or指令
or指令:邏輯或,按位進行或運算,運算規則是有1爲1
例:
mov al,01100011B and al,00111011B
或運算以後的結果是01111011B
有了這條指令後,咱們就能夠將咱們想要設置爲1的位置設置爲1,而其餘的位置不用改變
可使用[bx+idata]的方式來表示一個內存單元,他的段地址存放在ds中,偏移地址爲bx中的值+idata。
例:
mov ax,[bx+100]
這樣ax中存放的地址就是ds*10H+bx+100。
這樣的表示方式能夠用來表示數組,好比一個存放了三個數字的數組,下標分爲爲0、一、2,使用ds:bx定位到數組的第一個數,也就是數組下標爲0的位置,這樣若是想要將下標爲1的數字存放到ax中,就可使用mov ax,[bx+1],想要存放下標爲2的數字,只須要mov ax,[bx+2]。
除了使用[bx+idata]以外,還可使用[bx+si]和[bx+di]來表示內存地址(si和di不能拆成兩個8位寄存器使用),也就是使用bx寄存器中存放的值和si或di中的值相加來表示偏移地址。
總結一下內存地址的表示方式:
(1)[idata] 使用一個常量直接表示地址
(2)[bx] 使用一個變量表示內存地址
(3)[bx+idata] 使用一個變量和一個常量表示地址
(4)[bx+si] 使用兩個變量表示地址
(5)[bx+si+idata] 使用兩個變量和一個常量表示地址
當咱們對一個內存單元中的數據進行操做的時候必定要時刻明確如下幾點:
(1)數據從哪取?
也就是要能找到存放數據的內存單元的物理地址,前面說到過**物理地址=段地址*16+偏移地址**,用於表示一個內存單元物理地址的方法有如下五種:
(2)取數據的大小?
知道怎麼肯定內存單元的大小以後,下一個問題就是取多大數據,是一個字節,仍是一個字,當咱們使用mov ax,bx
時,由於ax和bx都是一個字的大小,因此編譯器知道咱們要取得數據是一個字大小,也就是兩個字節,或者當咱們使用mov ax,[0]
時,編譯器也知道咱們取的是一個字大小,由於ax寄存器是一個字大小,可是若是咱們僅僅是對一個內存地址進行操做,好比咱們但願給內存地址ds:[0]裏面存入1,若是是給ax賦值1,那麼編譯器知道咱們是對一個字的數據長度進行操做,那麼如今對內存進行操做,就須要本身定義須要操做的數據的長度,以上面那個問題爲例,對一個字長度的內存進行操做:mov word ptr [0],1
,這樣編譯器就知道咱們是對一個字長度的數據進行操做,若是是對一個字節長度進行操做就是mov byte ptr [0],1
。
注意:mov ax,bl
是錯誤的,由於這兩個寄存器的熟讀寬度不同。
(3)取完的數據存到哪?
一般,咱們想要知道數據存放的位置,是經過ds和偏移地址進行肯定,也就是數據從ds+偏移地址處進行取,一般狀況下,使用ds和si兩個寄存器表示取數據的位置,使用es和di兩個寄存器表示數據將要存放的位置,也就是將段地址存放在ds和es,偏移地址存放在si和di。
在咱們使用僞指令的時候,,好比下面這個例子,假設咱們在數據段定義了一段須要用到的數據:
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h mov ax,2 mov cx,9 s: add ax,ax loop s mov ax,4c00h int 21h code ends end
當咱們想要運行這個程序的時候,cs:ip是指向code段的,這樣就將咱們定義的數據當成是彙編指令所對應的機器碼,從而將數據變成了彙編指令執行,若是想要避免這種狀況的出現,咱們就要指定讓編譯器從哪邊開始執行,這就用到了start
僞指令,將上面的程序改爲這樣:
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h start: mov ax,2 mov cx,9 s: add ax,ax loop s mov ax,4c00h int 21h code ends end start
注意:最後的僞指令end,要改爲end start,這就是end的另外一個用途,告訴編譯器程序的入口在什麼地方。
上一篇裏面說過當咱們對一個內存單元中的數據進行操做的時候必定要時刻明確如下幾點:
其實除了這三點外,還有一點就是數據處理,因此在使用匯編語言進行編程的時候最重要的就是要處理好這四點:數據從哪取,取多少,取到的數據怎麼處理,處理完放到哪。
接着來講一下除法指令div,在使用出發指令的時候要注意如下問題:
(1)除數:除數有8位和16位兩種,存放在一個寄存器或者內存單元中。
(2)被除數:若是除數爲8位,被除數爲16位,則默認存放在AX中,若是除數爲16位,被除數爲32位,則高位存放在DX中,低位存放在AX中。
(3)結果:若是除數爲8位,則AL存儲除法操做的商,AH存儲除法操做的餘數,若是除數爲16位,則AX中存儲除法操做的商,DX中存儲除法操做的餘數。
使用1001除100爲例:
mov ax,1001 mov bl,100 div bl
首先能夠看出這是一個8位的除法,因此程序執行後,al=0AH(商),ah=1(餘數)。
驗證一下,這裏的03E9是十六進制的1001,64是十六進制的100:
咱們來看一下ax寄存器的狀況:
再以100001除100爲例:
mov dx,1 mov ax,86A1H mov bx,100 div bx
能夠看出這是一個16位的除法,100001的十六進制位186A1,能夠明顯的看出一個寄存器已經存放不了了,這是將高位存放在DX中,低位存放在AX中,也就是將1放在DX中,186A放在AX中,執行後AX中存放上商03E8(1000),DX中存放餘數1。
驗證:
執行一下,注意AX和DX:
咱們定義一個字節型數據時,用的是db(define byte),定義一個字型數據時,用的是dw(define word),除了這兩個之外,還有更大的數據寬度,雙字型數據dd用於定義dword(double word),咱們能夠到內存中驗證一下。
assume cs:code,ds:data,ss:stack data segment db 1 dw 1 dd 1 data ends stack segment dw 0,0 stack ends code segment start: mov ax,stack mov ss,ax mov,sp,16 mov ax,data mov ds,ax mov ax,4c00h int 21h code ends end start
這裏我在數據段中聲明瞭db類型的1,dw類型的1和dd類型的1,咱們到內存中看一下:
dup指令是一個操做符,用於數據的重複。
好比當我想要重複定義20個字節型的1,按照之前的方法咱們是這麼定義的:
db 1,1,1,1,1,1,1,1,1,1 db 1,1,1,1,1,1,1,1,1,1
可能定義20個也還行,可是若是是200個呢?這時有了dup指令就比較方便了,使用dup指令是這麼定義的:
db 200 dup (1)
語法格式:
db 重複的次數 dup (重複的字節型數據) dw 重複的次數 dup (重複的字型數據) dd 重複的次數 dup (重複的雙字型數據)
咱們來實驗一下:
這樣就直接定義了200個1。
操做符offset的做用取標號的偏移地址,直接舉個例子吧:
assume cs:codesg data segment db 0 data ends codesg segment start: mov ax,offset start mov ax,4c00h int 21h codesg ends end start
這裏取start標號的位置存放到ax中,start是這段程序的開頭,因此偏移位置爲0,這裏也就是將0存放到ax中,看一下內存中的結果:
這裏能夠看到編譯器直接識別到了0.
jmp指令是無條件轉移指令,用於實現位移(這裏部不分開講段內遠轉移和段內近轉移),好比:
assume cs:codesg codesg segment start: mov ax,0 jmp s add ax,1 s: inc ax mov ax,4c00h int 21h codesg ends end start
這裏執行到jmp s的時候就會跳過add ax,1這條指令,而跳轉到inc ax
(ax = ax + 1至關於在C語言裏寫的ax++),也就是這個程序執行完ax裏面的值是1,編譯器裏裏面看一下:
執行完ax裏面的值爲1,換個角度看:
這裏能夠看到,編譯器已經幫咱們轉成了地址,恰好是指向inc ax
那條指令。
那麼jmp指令是怎麼知道要跳轉到哪的呢?
咱們先來回憶一下CPU執行程序的過程:
(1)從CS:IP指向的內存單元讀取指令,將讀取到的指令存放到指令緩衝器。
(2)將IP的值修改成下一條要執行的指令的地址(IP = IP + 所讀取指令的長度)。
(3)執行指令,轉到第一步重複這個過程。
以上圖爲例,咱們分析一下jmp指令讀取和執行過程:
(1)CS=0B74,IP=0003,CS:IP指向指令EB04。
(2)將指令碼EB04讀取進入指令緩衝器。
(3)IP=IP+所讀指令的長度,即IP=0003+2=0005(此處的NOP是空指令可忽略,IP指向006)。
(4)CPU執行指令緩衝器中的指令EB04。
(5)指令執行後,IP指向下一個要執行的位置,也就是0009,CS:IP指向inc ax。
jcxz是有條件轉移指令,j是指jmp,cx是指cx寄存器,z是指zero,jcxz就是當cx等於0的時候跳轉,若是不爲零就順序執行,至關於C語言裏面的:
if(cx == 0){ jmp 標號; }
首先,咱們向仍然奮戰在武漢一線的醫護工做人員們致以最誠摯的敬意!同時也祝各位小夥伴新年快樂!
call指令的做用和jmp指令的做用相似,只是它比jmp指令多作了幾個工做,call指令會將當前的IP或者CS和IP壓入棧中,而後再進行跳轉,也就是說call指令是push ip
和jmp 標號
兩個指令的結合。
注:當執行到call指令時,當前的ip中的值是下一條指令的地址,詳見以前說過的CPU執行過程。
直接舉個例子:
assume cs:codesg,ss:stack stack segment db 8 dup (0) stack ends codesg segment start: mov ax,stack mov ss,ax mov sp,8 call s inc ax s: pop ax mov ax,4c00h int 21h codesg ends end start
首先聲明一個8個字節的棧,而後call s
,先將執行到這條指令時的IP壓棧,而後跳轉到標號爲s處,再將棧裏面的一個字型數據出棧,放到ax中:
這是咱們寫的程序,看一下當咱們執行到call s
時,下一條指令所在地址的值是0B75:000B,這時會將IP的值壓棧,看一下棧中的狀況:
能夠看到6和7的位置也就是棧底已經將000B壓棧了,下一步就是pop ax
,就是將棧中的數據存放到ax中,此時就是將000B存放到ax中,執行以後看一下ax的變化:
看到ax寄存器中存放的就是000B。
ret指令的做用是pop ip
,將棧中的數據出棧到ip寄存器。
一般狀況下,ret指令和call指令結合使用,call指令會將ip的值壓棧,而ret指令恰好將棧中的數據出棧,再返還給ip寄存器,也就意味着,程序在執行到call指令時,跳轉到其餘地方執行程序,在程序的結尾加上ret,也就將要執行的指令又回到了call指令下一步。
直接來看程序吧:
assume cs:codesg,ss:stack stack segment db 8 dup (0) stack ends codesg segment start: mov ax,stack mov ss,ax mov sp,8 call s inc ax s: ret mov ax,4c00h int 21h codesg ends end start
這裏是咱們的程序:
執行到call指令的時候,將ip壓棧,而後跳轉到ret指令,這時,ret指令將棧中的數據出棧到ip。
call指令先將000B壓棧,也就是指向inc ax
指令的地址,而後跳轉到0B75:000C,0B75:000C處的指令是ret,接着執行時,又將000B存放到ip,接着看圖:
ip中存放的是000B,也就是又跳轉到了inc ax
指令。
mul指令是乘法指令,乘法指令和 除法指令同樣,分爲8位和16位兩種:
(1)兩個相乘的數:兩個相乘的數,要麼都是8位,要麼都是16位。若是是8位,一個默認放在AL中,另外一個放在8位寄存器或內存字節單元中;若是是16位,一個默認在AX中,另外一個放在16位寄存器或者內存字單元中。
(2)結果:若是是8位乘法,結果默認存放在AX中;若是是16位乘法,結果高位默認在AX中存放,低位在AX中存放。
格式以下:
mul reg mul 內存單元
CPU內部的寄存器中,有一種特殊的寄存器(對於不一樣的處理機制,個數和結構均可能不一樣)具備如下三種做用。
(1)用來存儲相關指令的某些執行結果
(2)用來爲CPU執行相關指令提供行爲依據
(3)用來控制CPU的相關工做方式
這種特殊的寄存器被稱爲標誌寄存器。
ZF標誌位叫作零標誌位,它用於記錄相關指令執行後,其結果是否爲0,若是結果爲零,那麼zf=1,若是結果不爲0,那麼zf=0
好比:
mov ax,1 sub ax,1
執行以後,結果爲0,則zf=1。
PF標誌位是奇偶標誌位,它記錄相關指令執行後,其結果的全部bit位中1的個數是否爲偶數,若是1的個數爲偶數,pf=1,若是爲奇數,那麼pf=0。
好比:
mov al,1 add al,10
執行以後,結果爲00001011B,其中1的個數爲3個(奇數),則將PF置零。
SF標誌位是符號標誌位,它用於記錄相關指令執行後,其結果是否爲負,若是結果爲負,sf=1,若是結果非負,sf=0。
關於有符號數和無符號數:
對CPU而言,數字是沒有正負之分的,都是二進制數據,數字的正負都是認爲規定的,若是你把這個數字當成有符號數,那麼SF就有意義,若是你把數字當成是無符號數,那麼SF標誌位則沒有意義。
CF標誌位是進位標誌位,通常狀況下,在進行無符號數運算的時候,它記錄了運算結果的最高有效位向更高位的進位值,或從更高位的進位值。
注意:CF位是對於無符號數而言。
例:
mov al,97H mov al,98H ;執行後:al=FFH,CF=1(有借位) sub al,al ;執行後:al=0,CF=0(沒有借位)
OF標誌位是溢出標誌位,溢出是對於有符號數而言,好比8位有符號數,他能表示的範圍是-128~127
mov al,98 add al,99
執行完以後的值是197,很明顯超出了al寄存器能表示的範圍,這裏就產生了溢出,那麼OF位就置一。
若是你對彙編比較感興趣能夠深刻學習一下王爽老師的《彙編語言(第三版)》。