今天要介紹組合語言的循環指令 loop,結構如下:
p:
;循環內容
loop p
loop 會搭配 cx 暫存器
一起使用,執行到 loop 時會先將 cx 遞減一,接著判斷 cx 的值,如果不為零則跳到標號地址繼續執行,如果為零則退出循環,有點像 C 語言的 do while
語句。
int cx = 10;
do {
//循環內容
} while(cx > 0)
題目: 計算 2 x 3 並將結果存入 ax 暫存器。
組合語言有乘法指令,不過還沒有介紹就當不知道,先用加法來模擬,哈哈哈。
assume cs:code
code segment
start:
mov ax, 2
add ax, 2
add ax, 2
mov ax, 4c00h
int 21h
code ends
end start
很笨的寫法,乘以幾就寫幾次 add,這種寫法會有什麼問題呢? 如果以後想要改成乘以 1000 乘以 10000呢,是不是光一個簡單的運算就要寫 10000 行程式,當然不是這樣,程式厲害的地方就在於可以將重複循環的事交給電腦來做,只要將 add 的部分改成迴圈就可以了,電腦會照著我們寫的迴圈自動完成 add 的動作。
assume cs:code
code segment
start:
mov ax, 2
mov cx, 999 ; 2 x 1000
p: add ax, 2
loop p
mov ax, 4c00h
int 21h
code ends
end start
結果如下,最後 ax 暫存器內的值 07D0
剛好就是十進制的 2000
。
補充: 可以看到 DOSBox 視窗中我有使用 -p 命令
,前面介紹 Debug 工具時沒有提到現在補充說明,-p 可以用來跳過迴圈停在 loop 後的下一條指令上,不然以上面的例子使用 -t 命令我要按 1000 次才會離開迴圈...
再來看題目,將 1 - 10 數字存入記憶體 1000 - 1009 內。
前面有提到程式內可以用中括號 []
存取和寫入記憶體,要注意記憶體的單位是 字節
,而暫存器是 字
,所以直接用 16 位暫存器對記憶體寫入,一次會寫入兩個字節大小的數據,不過因為數值都是存放在暫存器低位的部分,所以其實不影響結果,當然也可以直接使用低位暫存器,這樣每次就只會寫入一個字節大小的數據。
初步構思如下:
mov ax, 1
mov ds:[0], ax ; [0]=1
inc ax
mov ds:[1], ax ; [1]=2
...
補充: inc 指令可將暫存器的內容遞增一,類似 C 語言的 i++
。
是不是有點像陣列,在高階語言遇到陣列,可以利用迴圈和變數去繞陣列,組合語言也是一樣,我們可以將中括號內的偏移地址換成暫存器,並搭配 loop 指令進行遞增,這樣可以精簡不少程式碼。
assume cs:code
code segment
start:
mov bx, 0100h
mov ds, bx
mov bx, 0 ; 偏移地址從0開始
mov ax, 1 ; 數值從1開始
mov cx, 10 ; 迴圈次數10
p: mov [bx], ax ; 將 ax 的值放入記憶體 [bx] 中
inc bx ; 遞增 bx
inc ax ; 遞增 ax
loop p
mov ax, 4c00h
int 21h
code ends
end start
結果如下:
看到這段程式碼:
mov ds:[0], ax
...
mov [bx], ax
其中 ds:
就是段前綴,為什麼要加 ds 前面文章已經提過,如果中括號內放的是常數,編譯器會將其當成 mov 0, ax
,如果放的是暫存器則預設會使用 ds
內容當段地址,那可以使用其他段暫存器嗎? 可以的,在存取記憶體時,可以使用段前綴明確指定要使用的段暫存器,不過令我好奇的是,編譯後的程式如何區分兩者呢。
我做了一個測試,將下面程式編譯後,用 Debug 工具查看記憶體的內容。
assume cs:code
code segment
start:
mov ax, ds:[0]
mov ax, ds:[bx]
mov ax, es:[0]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, ss:[bx]
mov ax, 4c00h
int 21h
code ends
end start
可以發現除了預設的 ds 暫存器之外,其他的暫存器編譯後都會包含段前綴,CPU 就能區分我們使用的偏移地址是屬於哪一個段暫存器。
今天介紹了組合語言的迴圈,用法和 do while 差不多,不過 loop 需要用 cx 暫存器判斷是否離開迴圈,所以次數受限於 cx 的大小,最多只能循環 65536 次,今天就到這裡摟,感謝大家觀看。