這一章主要介紹什麼是[BX]以及loop(迴圈)指令怎麼使用,loop和[BX]又怎麼樣相結合,段首碼又是什麼鬼,以及如何使用段首碼。 1、[BX]的概念 [BX]和[0]類似,[0]表示記憶體單元的偏移地址是0。要完整描述一個記憶體單元,需要兩種信息:記憶體單元的地址,記憶體單元的長度(類型)。[BX]同 ...
這一章主要介紹什麼是[BX]以及loop(迴圈)指令怎麼使用,loop和[BX]又怎麼樣相結合,段首碼又是什麼鬼,以及如何使用段首碼。 1、[BX]的概念 [BX]和[0]類似,[0]表示記憶體單元的偏移地址是0。要完整描述一個記憶體單元,需要兩種信息:記憶體單元的地址,記憶體單元的長度(類型)。[BX]同樣也表示一個記憶體單元,它的偏移地址在bx中,比如指令:mov ax,[bx]。這裡我們以一個程式為例:
1 assume cs:codesg 2 codesg segment 3 start: mov ax,2000H 4 mov ds,ax 5 mov al,[0] 6 mov bl,[1] 7 mov cl,[2] 8 mov dl,[3] 9 mov ax,4C00H 10 int 21H 11 codesg ends 12 end startView Code 進行編譯,連接之後,使用debug來調試這個程式。對於編譯器而言,[0]的中括弧是不存在的,所以會出現把0放到al中,把1放到bl中。所以,我們要想偏移地址為某個數,所對應的記憶體單元傳遞到寄存器當中,就需要進行兩步操作: mov bx,0 mov ax,[bx] 這樣做的話,就可以實現將偏移地址為0對應的記憶體單元的值傳遞給寄存器了。 2、loop 這是一個做迴圈操作的指令,指令格式:loop 標號。 CPU執行loop指令時,要進行兩個步驟的操作: (1)(cx)=(cx)-1(這裡的cx放在一個括弧裡面,意思是cx的值) (2)判斷cx是不是為零,不為零的話轉到標號出執行程式。 所以,loop和cx是有直接聯繫的,即cx的值影響著loop的執行結果。所以cx的值也就是指的是loop的迴圈次數,我們可以想到高級語言中,使用迴圈時(比如for迴圈),我們都會定義一個變數,來作為迴圈的條件,這個變數的值也就是迴圈的次數。 現在我們來看用彙編來計算2^2結果的代碼:
1 assume cs:code 2 code segment 3 mov ax,2 4 add ax,ax 5 6 mov ax,4c00h 7 int 21h 8 code ends 9 endView Code 計算2^3也是類似的,代碼如下:
1 assume cs:code 2 code segment 3 mov ax,2 4 add ax,ax 5 add ax,ax 6 7 mov ax,4c00h 8 int 21h 9 code ends 10 end 11View Code 那麼計算2^12,我們就要使用迴圈來做了,不然重覆的代碼太多,這個時候我們就要使用到loop指令了,代碼如下:
1 assume cs:code 2 code segment 3 mov ax,2 4 mov cx,11 5 s:add ax,ax 6 loop s 7 8 mov ax,4c00h 9 int 21h 10 code ends 11 endView Code 上面的代碼中,s就是標號,這個標號可以是任何你自己定義的內容,實際上s標明瞭一個地址,這個地址處有一個指令:add ax,ax。cx我們定義的值為11,因為要算2^12,所以要迴圈11次。迴圈時,cx會減1,迴圈一次減一次,直到cx為0,停止迴圈。我們可以使用debug調試編譯連接好的程式: 從上面的內容,我們可以得到cx和loop指令互相配合的三個關鍵點: (1)、在cx中存放迴圈次數 (2)、loop指令標號所標識的地址要在前面 (3)、要迴圈執行的程式段,寫在標號和loop指令之間。 3、loop和[bx]的聯合使用 我們先從幾個問題入手:計算ffff:0~ffff:b單元中的數據的和,結果存儲在dx中。 (1)、運算後的結果是否會超出dx所能存儲的值的範圍? ffff:0~ffff:b記憶體單元中的數據是位元組型數據,範圍是0~256,12個這樣的數據相加,結果是不會比65535更大的,因此dx中是可以存儲下的。 (2)、能夠將ffff:0~ffff:b直接累加到dx當中呢? 不行,因為ffff:0~ffff:b中的數據是8位的,不能直接加到16位寄存器dx中。 (3)、能否將ffff:0~ffff:b中的數據累加到dl中,並設置(dh)=0,從而實現累加到dx中呢? 不行,dl是一個8位寄存器,能容納的數據的範圍在0~255之間,ffff:0~ffff:b中的數據也是8位的,將12個8位的數據累加到一個8位寄存器中,可能會出現進位丟失的情況。 (4)、那麼到底應該怎樣將這段記憶體單元中的數據的和存儲到dx中呢? 就是使用一個16位寄存器來做中介,將記憶體單元中的8位數據賦值到一個16位寄存器ax中,再將這個ax中的數據加到dx上,從而使得運算對象的類型能夠的到匹配(ax,dx都是16進位)並且結果不會越界。 因為這裡要進行12次的相加,因此要使用到loop指令進行迴圈相加的操作,具體代碼如下:
1 assume cs:codeseg 2 codeseg segment 3 4 mov ax,0ffffh 5 mov ds,ax ;初始化ds:bx 指向ffff:0記憶體單元 6 mov bx,0 7 8 mov dx,0 ;初始化累加寄存器dx 9 mov cx,12 ;初始化迴圈計數寄存器cx 10 11 s: mov al,[bx] 12 mov ah,0 13 add dx,ax ;間接向dx中加上((ds)*16+(bx))單元中的數值 14 inc bx ;ds:bx指向下一個單元 15 loop s 16 17 mov ax,4c00h 18 int 21h 19 20 codeseg ends 21 endView Code 4、Debug的G命令和P命令 當我們對程式進行調試時,迴圈的次數很大時,我們不可能一次一次的執行t命令,因此要使用G命令或者P命令。G 迴圈結束的偏移地址,這樣就可以一次執行完迴圈的內容。P,直接執行完迴圈的內容。 5、一段安全的空間 當我們要直接向記憶體中寫入內容時,這段記憶體空間不應該存放系統或者其它程式的數據或者代碼,否則寫入會導致操作系統發生錯誤, DOS方式下,一般情況,0:200~0:2FF空間匯中沒有存放系統或者其它程式的數據或者代碼,以後我們寫入記憶體就直接寫到這段空間當中。 6、段首碼 指令 mov ax,[bx]中,記憶體單元的偏移地址由bx給出,而段地址預設在ds中,我們可以在訪問記憶體單元的指令中,顯示的給出記憶體單元的段地址所在的段寄存器,比如,mov ax,ds:[bx],這裡的段寄存器可以是ds,cs,es,ss,這些指令中的段寄存器在彙編語言中也被稱為段首碼。 7、段首碼的使用 首先我們考慮一個問題:將ffff:0~ffff:b中的數據複製到0:200~0:20b單元中。 分析: (1)0:200~0:20b也就是0020:0~0020:b,它們描述的是同一個記憶體空間。 (2)在迴圈中,源始單元 ffff:X 和目標單元 0200:X 的偏移地址X是變數,我們使用一個寄存器bx來存放 代碼如下:
1 assume cs:codeseg 2 codeseg segment 3 4 mov bx,0 ;偏移地址從0開始 5 mov cx,12 ;迴圈次數為12次 6 7 s: mov ax,0ffffh 8 mov ds,ax ;ds的值為0ffffh 9 mov dl,[bx] ;(dl)=((ds)*16+bx)將ffff:bx中的數據傳入到dl中 10 11 mov ax,0020h 12 mov ds,ax ;ds的值為0020h 13 mov [bx],dl ;((ds)*16+bx)=(dl)將dl中的數據傳入到0020:bx中 14 15 inc bx ;bx自增1 16 loop s 17 18 mov ax,4c00h 19 int 21h 20 21 codeseg ends 22 endView Code 當然,我們上面的代碼還有需要改進的地方,從上面的代碼可以看到,我們重覆設置了兩次ds的值,這樣做沒有錯,只是在迴圈的次數比較小的情況下,一旦迴圈的次數增到很大時,CPU就要進行很多次重覆的操作,這樣效率也就比較低了。因此,我們可以對代碼進行相應的改進:
1 assume cs:codeseg 2 codeseg segment 3 mov ax,0ffffh 4 mov ds,ax ;(ds)=0ffffh 5 6 mov ax,0020h 7 mov es,ax ;(es)=0020h 8 9 mov bx,0 ;(bx)=0,這個時候,ds:bx指向ffff:0,es:bx指0020:0 10 mov cx,12 11 12 s: 13 mov dl,[bx] ;(dl)=((ds)*16+(bx)),將ffff:bx中的數據傳入到dl 14 mov es:[bx],dl ;((es)*16+(bx))=(dl),將dl中的數據傳入0020:bx 15 16 inc bx 17 loop s 18 19 mov ax,4c00h 20 int 21h 21 22 codeseg ends 23 endView Code