之前的文章中介紹過 [0] 表示的是偏移地址為 0 的內存單元 。比如下面的指令
mov ax,[0]
就是將一個內存單元的內容送入 ax,這個內存單元的長度為 2 個字節(jié),是一個字型數據,偏移地址為 0 ,段地址在 ds 中,也就是這個內存單元的地址是 ds:0 ,它的物理地址是 (ds * 16 + 0)H。
除了可以傳輸字型數據,還可以傳輸字節(jié)型數據,比如下面代碼
mov al,[0]
就是將一個內存單元的地址送入 al 中,這個內存單元的長度是 1 字節(jié),存放字節(jié)型數據,偏移地址為 0 ,段地址在 ds 中,大白話說也就是將 ds:0 處的一個字節(jié)傳入到 al 中。
從上面兩個例子可以看出,假如要描述一個完整的一個內存單元,應該需要兩種信息:即內存單元的地址和內存單元的長度。
比如我們要讀取一個 10000H 的數據,你可能會需要下面這段代碼。
mov bx,1000H #將 1000H 放入 bx 寄存器中
mov ds,bx #將段寄存器 ds 的值設為 1000H mov al,[0]#從內存單元 1000:0 處讀出1字節(jié)內容放入 al 中。
但是表示內存地址的方式除了能夠直接指定其內存地址之外,還可以用一種間接尋址的方式,這就是 [bx],它表示的是一種寄存器間接尋址,也是一種偏移地址,同樣的,比如我們要讀取一個物理地址為 10001H 處的數據,使用 [bx] 這種方式的代碼如下
mov ax,1000H
mov ds,ax mov bx,1 mov ax,[bx]
這樣計算機就會尋找段地址為 1000H,偏移地址為 0001H 的數據放入到 ax 中。
它的中文解釋就是 把 [bx] 指向的地址中的內容,送入 ax 寄存器中。
比如下面這段代碼
mov ax,[bx]
它表示的就是將偏移地址為 bx 的數據,送入到 ax 中,送入的是 2 個字節(jié),也就是字型數據。
又比如下面這段代碼
mov al,[bx]
它表示的就是將偏移地址為 bx 的數據,送入到 al 中,送入的內存單元地址是 1 個字節(jié)的字節(jié)型數據。
[bx] 這種間接尋址的好處就是每次偏移地址不是固定的,這為我們接下來的循環(huán)指令奠定了基礎。
( ) 表示法
為了更方便的描述后面,我們使用 () 來表示一個寄存器或者內存單元中的內容。
這里需要注意一下,( ) 內的能夠表示的內容一般有三種類型:
寄存器名,比如 (ax) 就表示 ax 中的內容,(al) 就表示 al 中的內容。
段寄存器名,比如 (ds) 就表示段寄存器 ds 中的內容。
內存單元的物理地址,比如 ((ds) * 16 + (bx)),一個 20 位的數據。
我們知道,寄存器存儲的數據類型有兩種,字型和字節(jié)型,字型數據一般用 ax 這類寄存器來存儲,字節(jié)型數據一般用 ah 、al 這種寄存器來存儲。
同樣的,( ) 內的數據類型也有兩種,字型和字節(jié)型。比如 (al)、(bl)、(cl) 這種表示的數據就是字節(jié)型,而 (ax)、(bx)、(cx) 表示的數據就是字型。下面是幾類 ( ) 的一些用法:
ax 中的內容為 0020H:(ax) = 0020H;
2000:1000 處的內容為 0010H:(21000H) = 0010H;
mov ax,[2] 則表示為:ax = ((ds * 16) + 2);
mov [2],ax 則表示為:((ds) * 16 + 2) = (ax);
add ax,2 表示為:(ax) = (ax) + 2;
push ax 表示為:(sp) = (sp) - 2, ((ss * 16) + sp) = (ax);
pop ax 表示為:(ax) = ((ss) * 16) + sp), (sp) = (sp) + 2;
idata
idata 表示的就是立即數,這個概念就更簡單了,立即數顧名思義就是直接的數字,也就是常量。比如 mov ax,[0] ,其中的 0 就是立即數,即 idata = 0 ,所以 [立即數] = [idata],所以以后我們通常使用 idata 來表示常量,比如下面幾個例子
mov ax,[idata] 可以表示為 mov ax,[1]mov ax,[2]mov ax,[3]
mov ax,idata 可以表示為 mov ax,1 ? ?mov ax,2 ? ?mov ax,3
知道上面是 啥意思了吧?
[BX]
再來啰嗦一下 [bx] 的尋址方式,比如下面代碼
mov ax,[bx]
bx 中存放的數據作為一個偏移地址,這里用 EA 表示(沒有其他意思,只是單純地表示偏移地址),段地址在 ds 中,用 SA 表示(同 EA 的解釋),將 SA:EA 處的數據送入 ax 中,即 (ax) = ((ds) * 16 + (bx))。
可以將內存單元送入寄存器中,也可以將寄存器的數據送入到內存單元中,如下代碼所示
mov [bx],ax
就是將 ax 中的數據送入到 SA:EA 處,即 ((ds) * 16 + (bx)) = (ax)。
為了讓大家加深對 [bx] 的認識,我們通過一些匯編指令來認識一下程序的執(zhí)行過程,代碼如下
mov ax,2000H
mov ds,ax mov bx,1000H mov ax,[bx] inc bx inc bx mov [bx],ax inc bx inc bx mov [bx],ax inc bx mov [bx],al inc bx mov [bx],al
初始內存示意圖:
下面我們就按照每一行指令來分析一下
首先,mov ax,2000H 就是將 2000 送入 ax 中,mov ds,ax 就是將設置段地址為 2000 H,mov bx,1000H 就是將 1000 送入 bx 中,mov ax,[bx] 就是將 2000:1000 處的地址送入到 ax 中(因為段基址為 2000,偏移地址 dx 為 1000),2000H:1000H 處的指令是 00be,所以 ax = 00BEH ,存儲字型數據。
inc bx 就是將寄存器 bx 的值加 1,此處有兩條 inc 指令,所以執(zhí)行完成后 bx = 1002H,此處段基址:偏移地址為 2000H:1002H。
然后下面 (第七行指令)mov [bx],ax 就是將 ax 中的數據送入到 [bx] 中,也就是 1002H 處,指令執(zhí)行后,2000:1002 單元的內容為 BE,2000:1003 單元的內容為 00,存放字型數據,執(zhí)行完成后的示意圖如下
繼續(xù)執(zhí)行第 8、9 行的指令,inc bx ,執(zhí)行完成后 bx = 1004H,然后執(zhí)行第 10 行指令 mov [bx],ax ,指令執(zhí)行前:ds = 2000H,bx = 1004H,mov [bx],ax 相當于是把 ax 中的數據送到 2000:1004 處,指令執(zhí)行完成后,2000:1004 的單元內容為 BE ,如下示意圖所示
接下來執(zhí)行第 11 行指令,inc bx,執(zhí)行完成后 bx = 1005H,mov [bx],al 是把 al 中的數據送入內存 2000:1005 處,指令執(zhí)行完成后,2000:1005 處的單元內容為 BE,如下示意圖所示
繼續(xù)執(zhí)行指令,第13、14 行指令和 11 、12 行指令一樣,它的意思就是將 bx 的值加一之后,將 al 的值送入到指定地址處,執(zhí)行完成后的 ds = 2000H,bx = 1006H,所以 2000:1006 處的內容是 BE(al 存儲的數據),示意圖如下
想必大家跟完上面的流程后,應該對 [bx] 這個間接尋址方式有了比較深刻的認識。
下面想個問題,使用匯編編程計算 2 * 2 ,并將結果存儲在 ax 寄存器中。
這個思路還是比較簡單的,直接將 2 放在 ax 寄存器中,然后執(zhí)行 ax 的 add 操作就可以了,下面是匯編代碼
assume cs:codesg codesg segment mov ax,2 add ax,ax mov ax,4c00h int 21h codesg ends end
上面這段代碼中的計算量還比較低,但是如果要讓你計算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 呢,你難道要寫 n 個 add ax,ax 嗎?
assume cs:codesg
codesg segment mov ax,2 add ax,ax add ax,ax add ax,ax add ax,ax 。。。 mov ax,4c00h int 21h codesg ends end
這就很繁瑣啊,所以不能這么玩,那該怎么搞呢?這里就需要一種能夠循環(huán)之星 add ax,ax 的指令了,這個指令就是 Loop。
Loop 指令
Loop 指令能夠循環(huán)判斷是否執(zhí)行指定的指令,它的執(zhí)行流程就相當于我們 Java 中的 for 循環(huán)。
我們先來使用 Loop 改寫一下上面 n 個 2 相乘的代碼,然后再講解一下 Loop 的使用。
assume cs:codesg codesg segment mov ax,2 mov cx,8 s: add ax,ax loop s mov ax,4c00h int 21h codesg ends end
可以看到,我們使用 8 個 2 相乘的代碼被優(yōu)化的這么簡單,這就是 loop 指令的精髓所在。
其實關鍵代碼就是三條指令,即
mov cx,8
s: add ax,ax
loop s
翻譯過來的意思就是將 8 放在 cx 中,然后給 add ax,ax 處設置一個標號,然后執(zhí)行 s 循環(huán)。
loop 指令的格式是:loop 標號,CPU 執(zhí)行 loop 指令的時候,要進行兩步操作,第一步:(cx) = (cx) - 1,第二步:判斷 cx 的值,不為 0 則轉至標號(上面代碼是 s)處繼續(xù)執(zhí)行指令,如果為 0 則向下執(zhí)行(上面代碼中鄉(xiāng)下繼續(xù)執(zhí)行就是 mov ax,4c00h)。上面代碼中,我們把 8 送入了 cx 中,也就是說,cx 中存儲的就是執(zhí)行次數。
下面我們詳細介紹一下上面這段程序的執(zhí)行過程,從中體會一下 cx 和 loop s 是如何配合實現(xiàn)循環(huán)的。
(1) 執(zhí)行 cx,8 ,設置 cx = 8
(2) 執(zhí)行 add ax,ax(第 1 次)
(3) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 7,(cx) != 0 ,所以轉至 s 處
(4) 執(zhí)行 add ax,ax(第 2 次)
(5) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 6,(cx) != 0 ,所以轉至 s 處
(6) 執(zhí)行 add ax,ax(第 3 次)
(7) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 5,(cx) != 0 ,所以轉至 s 處
(8) 執(zhí)行 add ax,ax(第 4 次)
(9) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 4,(cx) != 0 ,所以轉至 s 處
(10) 執(zhí)行 add ax,ax(第 5 次)
(11) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 3,(cx) != 0 ,所以轉至 s 處
(12) 執(zhí)行 add ax,ax(第 6 次)
(13) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 2,(cx) != 0 ,所以轉至 s 處
(14) 執(zhí)行 add ax,ax(第 7 次)
(15) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 1,(cx) != 0 ,所以轉至 s 處
(16) 執(zhí)行 add ax,ax(第 8 次)
(15) 執(zhí)行 loop s 將 cx 的值 - 1,此時 (cx) = 0,(cx) == 0 ,所以轉至 s 處
(16) 執(zhí)行 mov ax,4c00h(循環(huán)結束)
從上面這個過程中,我們可以總結處用 cx 和 loop 指令相配合實現(xiàn)循環(huán)功能的 3 點注意事項:
在 cx 中存放循環(huán)次數。
loop 指令中的標號所標識的地址要在前面
要循環(huán)執(zhí)行的程序段,要寫在標號和 loop 指令的中間。
所以綜上所述,使用 Loop 和 cx 相配合實現(xiàn)的循環(huán)功能的結構如下:
mov cx,循環(huán)次數
s: 循環(huán)執(zhí)行的程序段 loop s
比如我們想用 Loop 循環(huán)計算出 123 * 456 這個值,就可以使用這種方式
assume cs:codesg codesg segment mov ax,0 mov cx,456 s:add ax,123 loop s mov ax,4c00h int 21h codesg ends end
編輯:黃飛
?
評論
查看更多