原來彙編中的循環是這麼玩兒的
彙編系列其實也在一直更新,只不過更新的頻率會挺慢的。。。由於白天一直忙於工作,空閑時間還要看書、學習各種技術棧,早上也要抽時間早期健身,晚上回家還要陪家人 + 學習,時間安排的滿滿當當,所以我就慢慢寫,各位讀者也別太著急,我其實真想再分一個自己出來。
之前的文章中介紹過 [0] 表示的是記憶體單元,它一般存儲在 ds 暫存器中,偏移地址為 0 。比如下面的指令
mov ax,[0]
就是將一個記憶體單元的內容送入 ax,這個記憶體單元的長度為 2 個位元組,正好存放一個字型數據,偏移地址為 0 ,段地址在 ds 中。這種定址方式相當於是直接定址。
比如下面程式碼
mov al,[0]
就是將一個記憶體單元的地址送入 al 中,這個記憶體單元的長度是 1 位元組,存放位元組型數據,偏移地址位 0 ,段地址在 ds 中。
所以要描述一個完整的一個記憶體單元,應該需要兩種資訊:即記憶體單元的地址和記憶體單元的長度。
比如我們要讀取一個 10000H 的數據,你可能會需要下面這段程式碼。
mov bx,10000H
mov ds,bx
mov al,[0]
上面這三條指令就把 10000H 讀取到了 al 中。
但是表示記憶體地址的方式不只有直接指定其記憶體地址,還可以用一種間接定址的方式,比如 [bx],它表示的是一種暫存器間接定址,也是一種偏移地址,同樣的,比如我們要讀取一個 10000H 的數據,使用 [bx] 這種方式的程式碼如下(假設 ds = 1000H)
mov bx,1
mov ax,[bx]
這樣電腦就會尋找段地址為 1000H,偏移地址為 0001H 的數據放入到 ax 中。
它的中文解釋就是 把 [bx] 指向的地址中的內容,送入 ax 暫存器中。
比如下面這段程式碼
mov ax,[bx]
它表示的就是將偏移地址為 bx 的數據,送入到 ax 中,送入的記憶體單元地址是 2 個位元組,存放字型數據。
又比如下面這段程式碼
mov al,[bx]
它表示的就是將偏移地址為 bx 的數據,送入到 al 中,送入的記憶體單元地址是 1 個位元組,存放位元組型數據。
[bx] 這種間接定址的好處就是每次偏移地址不是固定的,這為我們接下來的循環指令奠定了基礎。
為了更方便的描述後面,我們後面使用 () 來表示一個暫存器或者記憶體單元中的內容。
這裡需要注意一下,() 內的表示的元素一般有三種類型:
- 暫存器名,比如 (ax) 就表示 ax 中的內容,(al) 就表示 al 中的內容。
- 段暫存器名,比如 (ds) 就表示段暫存器 ds 中的內容。
- 記憶體單元的物理地址,比如 ((ds) * 16 + (bx)),一個 20 位的數據。
我們知道,暫存器存儲的數據類型有兩種,字型和位元組型,字型數據一般用 ax 這類暫存器來存儲,位元組型數據一般用 ah 、al 這種暫存器來存儲。
同樣的,() 內的數據類型也有兩種,字型和位元組型。比如 (al)、(bl)、(cl) 這種表示的數據就是位元組型,而 (ax)、(bx)、(cx) 表示的數據就是字型。
在了解完上述的這些知識點後,我們就可以來正式看一下 [bx] 了。
[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] 的認識,我們通過一些彙編指令來認識一下程式的執行過程,程式碼如下
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 指令,所以執行完成後 bx = 1002H,此處段基址:偏移地址為 2000H:1002H。
然後下面 (第七行指令)mov [bx],ax 就是將 ax 中的數據送入到 [bx] 中,也就是 1002H 處,指令執行後,2000:1002 單元的內容為 BE,2000:1003 單元的內容為 00,存放字型數據,執行完成後的示意圖如下
繼續執行第 8、9 行的指令,inc bx ,執行完成後 bx = 1004H,然後執行第 10 行指令 mov [bx],ax ,指令執行前: ds = 2000H,bx = 1004H,mov [bx],ax 相當於是把 ax 中的數據送到 2000:1004 處,指令執行完成後,2000:1004 的單元內容為 BE,2000:1005 的單元內容為 00 ,如下示意圖所示
接下來執行第 11 行指令,inc bx,執行完成後 bx = 1005H,mov [bx],al 是把 al 中的數據送入記憶體 2000:1005 處,指令執行完成後,2000:1005 處的單元內容為 BE,如下示意圖所示
繼續執行指令,第13、14 行指令和 11 、12 行指令一樣,它的意思就是將 bx 的值加一之後,將 al 的值送入到指定地址處,執行完成後的 ds = 2000H,bx = 1006H,所以 2000:1006 處的內容是 BE(al 存儲的數據),示意圖如下
想必大家跟完上面的流程後,應該對 [bx] 這個間接定址方式有了比較深刻的認識。
下面想個問題,使用彙編編程計算 2 * 2 ,並將結果存儲在 ax 暫存器中。
這個思路還是比較簡單的,直接將 2 放在 ax 暫存器中,然後執行 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
這就很繁瑣啊,所以不能這麼玩,那該怎麼搞呢?這裡就需要一種能夠循環之星 add ax,ax 的指令了,這個指令就是 Loop。
Loop 指令
Loop 指令能夠循環判斷是否執行指定的指令,它的執行流程就相當於我們 Java 中的 for 循環。
我們先來使用 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 相乘的程式碼被優化的這麼簡單,這就是 loop 指令的精髓所在。
其實關鍵程式碼就是三條指令,即
- mov cx,8
- s: add ax,ax
- loop s
翻譯過來的意思就是將 8 放在 cx 中,然後給 add ax,ax 處設置一個標號,然後執行 s 循環。
loop 指令的格式是:loop 標號,CPU 執行 loop 指令的時候,要進行兩步操作,第一步:(cx) = (cx) - 1,第二步:判斷 cx 的值,不為 0 則轉至標號(上面程式碼是 s)處繼續執行指令,如果為 0 則向下執行(上面程式碼中鄉下繼續執行就是 mov ax,4c00h)。上面程式碼中,我們把 8 送入了 cx 中,也就是說,cx 中存儲的就是執行次數。
下面我們詳細介紹一下上面這段程式的執行過程,從中體會一下 cx 和 loop s 是如何配合實現循環的。
(1) 執行 cx,8 ,設置 cx = 8
(2) 執行 add ax,ax(第 1 次)
(3) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 7,(cx) != 0 ,所以轉至 s 處
(4) 執行 add ax,ax(第 2 次)
(5) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 6,(cx) != 0 ,所以轉至 s 處
(6) 執行 add ax,ax(第 3 次)
(7) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 5,(cx) != 0 ,所以轉至 s 處
(8) 執行 add ax,ax(第 4 次)
(9) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 4,(cx) != 0 ,所以轉至 s 處
(10) 執行 add ax,ax(第 5 次)
(11) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 3,(cx) != 0 ,所以轉至 s 處
(12) 執行 add ax,ax(第 6 次)
(13) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 2,(cx) != 0 ,所以轉至 s 處
(14) 執行 add ax,ax(第 7 次)
(15) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 1,(cx) != 0 ,所以轉至 s 處
(16) 執行 add ax,ax(第 8 次)
(15) 執行 loop s 將 cx 的值 - 1,此時 (cx) = 0,(cx) == 0 ,所以轉至 s 處
(16) 執行 mov ax,4c00h(循環結束)
從上面這個過程中,我們可以總結處用 cx 和 loop 指令相配合實現循環功能的 3 點注意事項:
- 在 cx 中存放循環次數。
- loop 指令中的標號所標識的地址要在前面
- 要循環執行的程式段,要寫在標號和 loop 指令的中間。
所以綜上所述,使用 Loop 和 cx 相配合實現的循環功能的結構如下:
mov cx,循環次數
s:
循環執行的程式段
loop s
比如我們想用 Loop 循環計算出 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
彙編更新了幾篇文章了。
如果文章對你有幫助,小夥伴們三連走起呀!

