匯編學習筆記(4) -- [BX] 和 loop 指令


[BX]
和[0]有些類似,
[0] 代表偏移地址為0
 
 
要完整地描述一個內存單元,需要兩種信息:
①內存單元的地址;②內存單元的長度(類型)。
 
用[0]表示一個內存單元時,0表示單元的偏移地址,段地址默認在 ds中,
單元的長度(類型)可以由具體指令中的其他操作對象(比如說寄存器)指出。
 
[bx]同樣也表示一個內存單元,它的偏移地址在bx中,比如下面的指令
 
mov ax,[bx]

 

 
將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址在bx中,段地址在ds中。
 
mov al,[bx]

 

將一個內存單元的內容送入al,這個內存單元的長度為1字節(字單元)
存放一個字節,偏移地址在bx中,段地址在ds中。
 
 
[BX] 詳解
mov ax,[bx]

 

在bx中存放的數據作為偏移地址EA,段地址SA默認在ds中,將SA:EA處的數據送入ax
 
簡單來說
[bx]中的數據就是偏移地址,段地址默認在ds中
將 ds*16 + [bx] 所指向的內存單元中的數據 送入ax
 
 
mov [bx],ax

 

bx中存放的數據作為一個偏移地址EA,段地址SA默認在ds中
將ax中的數據送入內存SA:EA處。
簡單來說
ax中的數據放入 ds * 16 + [bx] 所指向的內存單元
 
 
例題
mov ax,2000H mov ds,ax mov bx,1000H mov ax,[bx] inc bx //代表bx中的內容+1,相當於高等語言中的++
inc bx mov [bx],ax inc bx inc bx mov [bx],ax inc bx mov bx,al inc bx

 

程序和內存中的情況如上所示,寫出程序執行后,21000H ~ 21007H單元中的內容
 
 
分析
(1)先看一下程序的前3條指令:
mov ax,2000H
mov ds,ax
mov bx,1000H

 

這3條指令執行后,ds=2000H,bx=1000H。
 
(2)接下來,第4條指令:
mov ax,[bx]

 

指令執行前: ds=2000H,bx=1000H
將 ds*16 + bx(即21000) 指向的內存單元 中的數據 存入 ax
ax = 00BE
 
(3)接下來,第5、6條指令:
inc bx inc bx

 

這兩條指令執行前bx=1000H,執行后bx=1002H.
inc 使bx里的數據自增1
 
(4)接下來,第7條指令:
mov[bx] ,ax

 

指令執行前: ds=2000H,bx=1002H,ax = 00BE
將ax里的數據送入2000:1002處
指令執行后,2000:1002單元的內容為BE,2000:1003單元的內容為00。
 
 
(5)接下來,第8、9條指令:
inc bx inc bx

 

這兩條指令執行前bx=1002H,執行后bx=1004H
 
 
(6)接下來,第10條指令:
mov[bx],ax

 

指令執行前:ds=2000H,bx=1004H,
將ax中的數據送入內存2000:1004處
指令執行后,2000:1004單元的內容為BE,2000:1005單元的內容為00。
 
 
(7)接下來,第11條指令:
inc bx

 

這條指令執行前bx=1004H,執行后bx=1005H。
 
 
(8)接下來,第12條指令:
mov[bx],al

 

指令執行前:ds=2000H,bx=1005H
將al中的數據送入內存2000:1005處
指令執行后,2000:1005單元的內容為BE。
 
 
(9)接下來,第13條指令:
inc bx

 

這條指令執行前bx=1005H,執行后bx=1006H.
 
 
(10)接下來,第14條指令:
mov[bx],al

 

指令執行前:ds=2000H,bx=1006H
則mov [bx],al 將把al中的數據送入內存2000:1006處。指令執行后,2000:1006單元
的內容為BE。
程序執行后,內存中的情況如圖所示。
 
 
 
 
 
 
loop
loop 指令的格式是: loop標號
 
 
通常情況下,我們用loop指令實現循環,cx中存放循環次數
 
 
例子
計算2^3
assume cs:code code segment mov ax,2 add ax,ax add ax,ax mov ax,4c00h int 21h code ends end

 

同理,計算2^12需要11個add ax,ax
 
使用loop
assume cs:code code segment mov ax,2 mov cx,11   //這里的cx存儲的是循環次數,11就是循環11次
 s:add ax,ax loop s mov ax,4c00h int 21h code ends end

 

s是標號,不能太長
該程序中它實際標識了一個地址,這個地址有一個指令:add ax,ax(地址范圍是標號后面到loop)
 
loop s
CPU執行loop指令的時候,要進行兩步操作:
①先讓cx中的數據 - 1
②判斷cx中的值,不為零則轉至標號處執行程序(循環跳轉),如果為零則向下執行(循環結束)
 
 
例題:計算123 * 236
assume cs:code code segment mov ax,0 mov cx,236 s: add ax,123 loop s mov ax,4c00h int 21h code ends end

 

注:如果反過來,將236*123,程序改成s: add ax,236
則只需要123次循環即可完成
 
 
 
在debug里跟蹤loop程序
 
計算fffr:0006單元中的數乘以3,結果存儲在dx中
 
分析一波
(1)運算后的結果是否會超出dx所能存儲的范圍?
fff:0006單元中的數是一個字節型的數據,范圍在0~255之間
則用它和3相乘結果不會大於65535,可以在dx中存放下
 
(2) ffff:6單元是一個字節單元,ax是一個16位寄存器,數據的長度不一樣,如何賦值?
 
注意,我們說的是“賦值”,就是說,讓 ax 中的數據的值(數據的大小)和ff:0006單元中的數據的值(數據的大小)相等
8位數據01H和16位數據0001H的數據長度不一樣,但它們的值是相等的。
 
那么我們如何賦值?設ffff:0006單元中的數據是XXH,若要ax中的值和ffff:0006單元中的相等,
ax中的數據應為00XXH。
所以,若實現 fffr:0006單元向ax賦值,應該令 ah 為0,al 為 ffff:0006 內的數據。
 
程序:
assume cs:code code segment mov ax,0ffffh mov ds,ax mov bx,6    //設置ds:bx指向ffff:6
 mov al,[bx] mov ah,0    //設置al為ds:bs中的數據,ah為0
 mov dx,0    //累加寄存器清零
 mov cx,3    //設置循環次數
 s : add dx,ax loop s //循環計算三次
 mov ax,4c00h int 21h     //程序返回
code ends end

 

第一條指令mov ax,0ffff
在匯編源程序中,數據!絕對!不能以字母開頭,所以要在前面加0。
比如,9138h 在匯編源程序中可以直接寫為“9138h”,而A000h在匯編源程序中要寫為“0A000h”
 
如果是在dosbox里的debug中,數據可以 以字母開頭
 
 
可以使用g命令執行到 指定的位置
g 0012 代表從cs:ip一直執行到cs:0012處
 
 
使用p命令自動循環

 

 

當出現 loop 0012時(循環開始) 即可使用p,直接一次執行完循環
 
也可以使用g跳到loop后面的位置
g 0016
 
 
debug 和 匯編編譯器masm 對指令的不同處理
 
mov ax,[0]
debug中表示 將 ds:0處的 數據 送入ax中。
但是在匯編源程序中,指令“mov ax,[0]”被編譯器當作指令“mov ax,0”處理。
 
例子:將內存2000:0 , 2000:2 , 2000:3單元中的數據送入al,bl,cl,dl中
 
在debug中:
mov ax,2000 mov ds,ax mov al,[0] mov bl,[1] mov cl,[2] mov dl,[3]

 

 
在匯編源程序中:
assume cs:code code segment mov ax,2000h mov ds, ax mov al,[0] mov bl,[1] mov cl,[2] mov dl,[3] mov ax, 4c00h int 21h code ends end

 

debug中運行:
 

 

masm編譯並運行
 
 
很明顯
以 mov ax,[0] 為例
debug和masm中的解釋各不相同
 
debug: mov al,[0000]
masm: mov al,00
 
在匯編源程序中,我們可以使用[bx]來實現同樣的效果
mov ax,2000h
mov ds,ax
mov bx,0
mov al,[bx]
 
這樣有點麻煩,所以,可以在 [bx]前面加上 段寄存器
mov ax,2000h
mov as,ax
mov al,ds:[0]
 
比較一下匯編源程序中以下指令的含義。
“mov al,[0]”,含義:將常量0送入al中(與 mov al,0含義相同);
“mov al,ds:[0]”,含義: 將ds * 16 + [0] 指向的 內存單元中的數據送入al中;
“mov al,[bx]”,含義:ds *16+ bx 指向的內存單元中的數據送入al中;
“mov al,ds:[bx]”,含義:與“mov al,[bx]”相同。
 
在匯編源程序中
要想使用 [...],就必須在前面加 ds:
如果不加,masm就會認為[...] 為 數據而非偏移地址
 
如果是[bx] masm同樣可以將 bx里的數據 作為 偏移地址
當然,你也可以在前面 加段寄存器名,指定位置
 
 
 
 
loop 和[bx] 的聯合應用
例題計算ffff:0 ~ ffff:b 單元中的數據和 結果存儲在dx中
 
先分析一下:
1 運算后的結果是否會超過dx所能存儲的范圍?
ffff:0 ~ ffff:b 內存單元中的數據都是字節型數據,范圍在0 ~ 266之間
12個這樣的數據相加,結果不會大於65536,可以存放
 
2 我們能否將ffff:0 ~ ffff:b中的數據直接累加到dx中?
不行,因為ffff:0 ~ ffff:b 中的數據是8位
不能直接加到16位寄存器中
 
3 我們能否將ffff:0 ~ ffff:b中的數據累加到dl中 並設置 dh數據為 0 從而實現累加到dx中?
不行 因為dl是8位寄存器 能容納的數據的范圍在 0~ 255 之間
很容易造成進位丟失
 
主要是兩個運算對象長度不匹配 和 數據大於寄存器造成丟失
 
目前的方法是使用一個16位寄存器作為中介
將內存單元中的8位數據賦值到一個16位寄存器ax中
再將ax中的數據加到dx上
從而使兩個運算對象的類型匹配且結果不會超界
 
 
使用loop和[bx]解決
assume cs:code code segment mov ax,0ffffh mov ds,ax mov bx,0  ;初始化ds:bx指向ffff:0 mov bx,0 ;初始化累加寄存器dx mov cx,12 ;設置循環次數位12 S: mov al,[bx] mov ah,0 add dx,ax ;間接向dx中加上 ds*16 + bx所指向的 內存單元 里的值 inc bx ;讓ds:bx指向下一個單元 loop S mov ax,4c00h int 21h code ends end

 

 
使用循環時
如果遇到必須在每次循環的時候按照同一種方法來改變要訪問的內存單元的地址時
就不能用常量來給出內存單元的地址(比如,[0]、[1]、[2]中,0、1、2是常量)
而是應用變量。
“mov al,[bx]”中的bx就可以看作一個代表內存單元地址的變量
我們可以不寫新的指令,僅通過改變 bx 中的數值,改變指令訪問的內存單元。
 
 
 
 
段前綴
指令 mov ax,[bx]中
內存單元的偏移地址由bx給出
而段地址默認在ds中
可以在這種指令中顯示地給出內存單元的段地址所在的段寄存器
 
比如:
mov ax,ds:[bx]

 

將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址在bx中,段地址在ds 中。
 
 
mov ax,cs:[bx]

 

將一個內存單元的內容送入ax,這個內存單元的長度為⒉字節(字單元)
存放一個字,偏移地址在bx中,段地址在cs 中。
 
 
mov ax,ss:[bx]

 

將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址在bx中,段地址在ss中。
 
 
mov ax,es:[bx]

 

將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址在bx中,段地址在es中。
 
 
mov ax,ss:[0]

 

將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址為0,段地址在ss中。
 
 
mov ax,cs:[0]

 

將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元)
存放一個字,偏移地址為0,段地址在cs中。
 
這些出現在訪問內存單元的指令中,用於顯式地指明內存單元的段地址的
“ds:”“cs:”“ss:”“es:”,在匯編語言中稱為段前綴。
 
 
 
一段安全的空間
在8086模式中,隨意向一段內存空間寫入內容是很危險的
因為這段空間中可能存放着重要的系統數據或代碼。
 
比如下面的指令:
mov ax,1000h mov ds,ax mov al,0 mov ds:[0],al

 

這種做法是不合理的
因為我們並沒有論證過1000:0中是否存放着重要的系統數據或代碼
如果1000:0中存放着重要的系統數據或代碼,“mov ds:[0],al”將其改寫,將引發錯誤。
 
dos方式下
一般情況,0:200 ~ 0:2ff這段空間沒有數據
 
 
 
段前綴的使用
將內存ffff:0 ~ ffff:b單元中的數據復制到0:200~0:20b單元中
 
(1)0:200~0:20b單元等同於0020:0~0020:b單元,它們描述的是同一段內存空間。
 
(2)復制的過程應用循環實現,簡要描述如下。
初始化:
x=0
循環12次:
將ffff:x單元中的數據送入 0020:x(需要用一個寄存器中轉)
x = x + 1
 
(3)在循環中,源始單元 ffff:x 和 目標單元0020:x 的 偏移地址 x 是變量
我們用bx來存放。
 
(4)將0:200~0:20b 用0020:0~0020:b 描述
就是為了使目標單元的偏移地址和源始單元的偏移地址從同一數值0開始。
 
程序
assume cs:code code segment mov bx,0   ;(bx)=0,偏移地址從О開始 mov cx,12 ;循環12次 s: mov ax,0ffffh mov ds,ax ;(ds) = Offffh mov dl,[bx] ;將(ds)*16+(bx) 指向的內存單元中的數據 送入dl mov ax,0020h mov ds,ax ;(ds)=0020h mov [bx],dl ; ( (ds)*16+(bx))= (dl),將中dl的數據送入0020:bx inc bx ; (bx)=(bx)+1 loop s mov ax, 4c00h int 21h code ends end

 

 
因源始單元ffff:x 和 目標單元 0020:X 相距大於64KB
在不同的64KB段里,程序中,每次循環要設置兩次 ds
這樣做是正確的,但是效率不高。
我們可以使用兩個段寄存器分別存放 源始單元fffd:x和目標單元 0020:x 的段地址
這樣就可以省略循環中需要重復做12次的設置ds的程序段。
 
改進版
assume cs:code code segment mov ax,0ffffh mov ds,ax ;(ds)=0ffffh mov ax,0020h mov es,ax ;(es)=0020h mov bx,0      ;(bx)=0,此時ds:bx指向ffff:0 es:bx指向0020:0 mov cx,12     ;(cx)=12,循環12次 s: mov dl,[bx] ;(dl)= ( (ds)*16+(bx)),將ffff:bx中的數據送入dl; mov es:[bx],dl ; ( (es)*16+(bx))= (dl),將dl中的數據送入0020:bx inc bx ;(bx)=(bx)+1 loop s mov ax,4c00h int 21h code ends end

 

 
程序中,使用es存放目標空間0020:0~0020:b的段地址
用ds存放源始空間ffff:0~ffff:b的段地址
 
在訪問內存單元的指令“mov es:[bx],al”中,顯式地用段前綴“es:”給出單元的段地址
這樣就不必在循環中重復設置ds。
 
 
 
 參考: 王爽 - 匯編語言 和 小甲魚零基礎匯編
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM