一.ret指令用棧中的數據,修改IP的內容,從而實現近轉移;api
CPU執行ret指令時,進行下面兩步操做:框架
a) (1)(IP)=((ss)*16+(sp))oop
b) (2)(sp)=(sp)+2spa
二.retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移;設計
CPU執行retf指令時,進行下面兩步操做:code
a) (1)(IP)=((ss)*16+(sp))orm
b) (2)(sp)=(sp)+2ip
c) (3)(CS)=((ss)*16+(sp))內存
d) (4)(sp)=(sp)+2字符串
三.能夠看出,若是咱們用匯編語法來解釋ret和retf指令,則:
a) CPU執行ret指令時,至關於進行:
pop IP
b).CPU執行retf指令時,至關於進行:
pop IP
pop CS
四.call 指令
CPU執行call指令,進行兩步操做:
a) (1)將當前的 IP 或 CS和IP 壓入棧中;
b) (2)轉移。
call 指令不能實現短轉移,除此以外,call指令實現轉移的方法和 jmp 指令的原理相同。
解釋:
n call 標號(將當前的 IP 壓棧後,轉到標號處執行指令)
n CPU執行此種格式的call指令時,進行以下的操做:
n (1) (sp) = (sp) – 2
((ss)*16+(sp)) = (IP)
n (2) (IP) = (IP) + 16位位移
call 標號
n 16位位移=「標號」處的地址-call指令後的第一個字節的地址;
n 16位位移的範圍爲 -32768~32767,用補碼錶示;
n 16位位移由編譯程序在編譯時算出。
五.綜合實例演示:
看下面一段簡單的代碼:
Mov ax,0
Call s
Mov bx,0
S:add ax,1
Ret
解釋以下:
Call s:實際上就是調用s處的子程序。並將Mov bx,0這條指令所對應的偏移地址入棧,此處爲何要入棧呢?實際上就是爲了方便ret指令取出ip。
Ret指令實際上就是返回s,能使程序從mov bx,0處執行。
六.call 和 ret 的配合使用
1.例子1:
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s
mov bx,ax ;(bx) = ?
mov ax,4c00h
int 21h
s: add ax,ax
loop s
ret
code ends
end start
咱們來看一下 CPU 執行這個程序的主要過程:
n (1)CPU 將call s指令的機器碼讀入,IP指向了call s後的指令mov bx,ax,而後CPU執行call s指令,將當前的 IP值(指令mov bx,ax的偏移地址)壓棧,並將 IP 的值改變爲標號 s處的偏移地址;
n (2)CPU從標號 s 處開始執行指令,loop循環完畢,(ax)=8;
n (3)CPU將ret指令的機器碼讀入,IP指向了ret 指令後的內存單元,而後CPU 執行 ret 指令 ,從棧中彈出一個值(即 call 先前壓入的mov bx,ax 指令的偏移地址)送入 IP 中。則CS:IP指向指令mov bx,ax;
n (4)CPU從 mov bx,ax 開始執行指令,直至完成。
2.例子2
n 咱們看一下程序的主要執行過程:
n (1)前三條指令執行後,棧的狀況以下:
n 2)call 指令讀入後,(IP) =000EH,CPU指令緩衝器中的代碼爲 B8 05 00;
CPU執行B8 05 00,首先,棧中的狀況變爲:
而後,(IP)=(IP)+0005=0013H。
n (3)CPU從cs:0013H處(即標號s處)開始執行。
n (4)ret指令讀入後:(IP)=0016H,CPU指令緩衝器中的代碼爲 C3;CPU執行C3,至關於進行pop IP,執行後,棧中的狀況爲:
(IP)=000EH;
n (5)CPU回到 cs:000EH處(即call指令後面的指令處)繼續執行。
3. 從上面的討論中咱們發現,能夠寫一個具備必定功能的程序段,咱們稱其爲子程序,在須要的時候,用call指令轉去執行。
4. 但是執行完子程序後,如何讓CPU接着call指令向下執行?
5. call指令轉去執行子程序以前,call指令後面的指令的地址將存儲在棧中,因此能夠在子程序的後面使用 ret 指令,用棧中的數據設置IP的值,從而轉到 call 指令後面的代碼處繼續執行。
七.mul 指令
n 因下面要用到,咱們介紹一下mul指令,mul是乘法指令,使用 mul 作乘法的時候:
n (1)相乘的兩個數:要麼都是8位,要麼都是16位。
8 位: AL中和 8位寄存器或內存字節單元中;
16 位: AX中和 16 位寄存器或內存字單元中。
n (2)結果
8位:AX中;
16位:DX(高位)和AX(低位)中。
n 格式以下:
mul reg
mul 內存單元
例子一:
n 例如:
n (1)計算100*10
100和10小於255,能夠作8位乘法,程序以下:
mov al,100
mov bl,10
mul bl
結果: (ax)=1000(03E8H)
例子二:
n (1)計算100*10000
100小於255,可10000大於255,因此必須作16位乘法,程序以下:
mov ax,100
mov bx,10000
mul bx
結果: (ax)=4240H,(dx)=000FH
(F4240H=1000000)
8.參數和結果傳遞的問題
n 咱們設計一個子程序,能夠根據提供的N,來計算N的3次方。
n 這裏有兩個問題:
n (1)咱們將參數N存儲在什麼地方?
n (2)計算獲得的數值,咱們存儲在什麼地方?
很顯然,咱們能夠用寄存器來存儲,能夠將參數放到 bx 中 ;由於子程序中要計算 N×N×N ,可使用多個 mul 指令,爲了方便,可將結果放到 dx 和 ax中。
n 子程序:
n 說明:計算N的3次方
n 參數: (bx)=N
n 結果: (dx:ax)=N∧3
cube:mov ax,bx
mul bx
mul bx
ret
n 用寄存器來存儲參數和結果是最常使用的方法。對於存放參數的寄存器和存放結果的寄存器,調用者和子程序的讀寫操做偏偏相反:
n 調用者將參數送入參數寄存器,從結果寄存器中取到返回值;
n 子程序從參數寄存器中取到參數,將返回值送入結果寄存器。
9.批量數據的傳遞
n 前面的例程中,子程序 cube 只有一個參數,放在bx中。若是有兩個參數,那麼能夠用兩個寄存器來放,但是若是須要傳遞的數據有3個、4個或更多直至 N個,咱們怎樣存放呢?
n 寄存器的數量終究有限,咱們不可能簡單地用寄存器來存放多個須要傳遞的數據。對於返回值,也有一樣的問題。
n 在這種時候,咱們將批量數據放到內存中,而後將它們所在內存空間的首地址放在寄存器中,傳遞給須要的子程序。
n 對於具備批量數據的返回結果,也可用一樣的方法。
以下代碼:
10.寄存器衝突的問題
n 設計一個子程序:
n 功能:將一個全是字母,以0結尾的字符串,轉化爲大寫。
n 分析
應用這個子程序 ,字符串的內容後面定要有一個0,標記字符串的結束。子程序能夠依次讀取每一個字符進行檢測,若是不是0,就進行大寫的轉化,若是是0,就結束處理。
因爲可經過檢測0而知道是否己經處理完整個字符串 ,因此子程序能夠不須要字符串的長度做爲參數。咱們能夠用jcxz來檢測0。
添加主框架
assume cs:code
data segment
db 'conversation',0
data ends
n 代碼段中相關程序段以下:
mov ax,data
mov ds,ax
mov si,0
call capital
其中si運用了屢次,怎麼避免呢?
從上而的問題中,實際上引出了個通常化的問題:子程序中使用的寄存器,極可能在主程序中也要使用,形成了寄存器使用上的衝突。
那麼咱們如何來避免這種衝突呢 ?粗略地看,咱們能夠有兩個方案:
n (1)在編寫調用子程序的程序時 ,注意看看子程序中有沒有用到會產生衝突的寄存器,若是有,調用者使用別的寄存器;
n (2)在編寫子程序的時候,不要使用會產生衝突的寄存器。
咱們編寫子程序的標準框架以下:
子程序開始:子程序中使用的寄存器入棧
子程序內容
子程序使用的寄存器出棧
返回(ret、retf)
以下代碼:
n capital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
n 要注意寄存器入棧和出棧的順序。