這篇文章旨在釐清組合語言中堆疊操作的地址計算細節,透過一個具體的範例程式,詳細說明 push 與 pop 指令對堆疊指標(SP)的影響,以及如何正確設定初始堆疊指標與資料來源指標。
在 8086 架構中,堆疊相關的關鍵暫存器與指令包括:
這是理解堆疊地址計算的核心:
[SS:SP],低位元組寫入 [SS:SP+1]。簡單說,SP 先指向下一個可用位置,再存入資料。[SS:SP] 讀取低位元組,[SS:SP+1] 讀取高位元組,組合成字資料,然後將 SP 加 2。即 先取資料,SP 再後移。假設段 A 有 15 個字資料(1 到 0FH),段 B 預留 8 個字空間。目標是將 A 段的前 8 個字(偏移 0~14H)依序壓入 B 段作為堆疊,最終 B 段從低地址到高地址應存放 1, 2, ..., 8。
mov ax, b ; 設定堆疊段
mov ss, ax
mov sp, _______ ; ★ 填空 1:初始 SP 值
mov ax, a ; 設定資料段
mov es, ax
mov bx, _______ ; ★ 填空 2:來源資料偏移
mov cx, 8
loop1:
push es:[bx] ; 壓入資料
sub bx, 2 ; 指向前一個字
loop loop1
B 段有 8 個字,佔 16 位元組(偏移 0~0FH)。第一次 PUSH 會先將 SP 減 2。為了讓第一筆資料(A 段第 8 個字,值 8)落在 B 段第 8 個字位置(偏移 0EH~0FH),SP 減 2 後必須指向 0EH。因此初始 SP = 0EH + 2 = 10H。
驗證:SP=10H → PUSH 時 SP=0EH → 寫入偏移 0EH/0FH(第 8 個字)。正確。
A 段第 8 個字(值 8)的偏移為 (8-1)*2 = 14 = 0EH。迴圈每次 sub bx, 2 往前取值,依序取得 8, 7, ..., 1。
assume cs:codeseg
a segment
dw 1,2,3,4,5,6,7,8,9,0aH,0bH,0cH,0dH,0eH,0fH
a ends
b segment
dw 0,0,0,0,0,0,0,0
b ends
codeseg segment
start:
mov ax, b
mov ss, ax
mov sp, 10H ; 初始堆疊指標
mov ax, a
mov es, ax
mov bx, 0EH ; 來源偏移:第 8 個字
mov cx, 8
loop1:
push es:[bx]
sub bx, 2
loop loop1
mov ax, 4c00H
int 21H
codeseg ends
end start
執行後查看記憶體,B 段(SS:0000)將依序顯示:
0000: 0001 0002 0003 0004 0005 0006 0007 0008
完全符合預期。同樣邏輯適用於 POP 操作,讀者可自行嘗試。
堆疊操作的地址計算關鍵在於 SP 的預減/預增機制。設定初始 SP 時,必須考慮第一次操作前的指標變化;存取資料時,索引暫存器(BX/SI/DI)直接指向目標資料當前位址。掌握這一點,即可避免堆疊溢位、資料錯位等常見錯誤。