間接尋址
間接尋址允許寄存器像指針變量一樣運作。要指出寄存器像一個指針一樣被間接使用,需要用方括號([])將它括起來。例如:
1 mov ax, [Data] ; 一個字的標準的直接內(nèi)存地址
2 mov ebx, Data ; ebx = & Data
3 mov ax, [ebx] ; ax = *ebx
因為AX可以容納一個字,所以第三行代碼從EBX儲存的地址開始讀取一個字。如果用AL替換AX,那么只有一個字節(jié)會被讀取。認識到寄存器不像在C中的變量一樣有類型是非常重要的。到底EBX具體指向什么完全取決于使用了什么指令。而且,甚至EBX是一個指針這個事實都完全取決于使用的指令。如果EBX錯誤地使用了,通常不會有編譯錯誤;但是,程序?qū)⒉粫_運行。這就是為什么相比于高級語言匯編程序較容易犯錯的原因之一。
所有的32位通用寄存器(EAX,EBX,ECX,EDX)和指針寄存器(ESI,EDI)都可以用來間接尋址。一般來說,16位或8位的寄存器是不可以的。
子程序的簡單例子
子程序是代碼中的一個的獨立的單元,它可以使用在程序的不同的地方。換句話說,一個子程序就像一個C中的函數(shù)。可以使用跳轉(zhuǎn)來調(diào)用子程序,但是返回會是一個問題。如果子程序要求能使用在程序中的任何地方,它必須要返回到調(diào)用它的代碼段處。因此,子程序的跳轉(zhuǎn)返回最好不要硬編碼為標號。下面的代碼展示了如何使用JMP指令的間接方式來做這件事。此指令方式使用一個寄存器的值來決定跳轉(zhuǎn)到哪(因此,這個寄存器與C中的函數(shù)指針非常相似。) 下面使用子程序的方法來重寫第一章中的第一個程序。
子程序get_int使用了一個簡單,基于寄存器的調(diào)用約定。它認為EBX寄存器中存的是輸入雙字的儲存地址而ECX寄存器中存的是跳轉(zhuǎn)返回指令的地址。25行到28行,使用了ret1標號來計算返回地址。在32行到34行,使用了$運算子來計算返回的地址。$運算子返回出現(xiàn)$這一行的當前地址。$+7表達式計算在36行的MOV指令的地址。
這兩種計算返回地址的方法都是不方便的。第一種方法要求為每一次子程序調(diào)用定義一個標號。第二種方法不需要標號,但是需要仔細的思量。如果使用了近跳轉(zhuǎn)來替代短跳轉(zhuǎn),那么與$相加的數(shù)就不會是7!幸運的是,有一個更簡單的方法來調(diào)用子程序。這種方法使用堆棧。
堆棧
許多CPU都支持內(nèi)置堆棧。堆棧是一個先進后出(LIFO)的隊列。它是以這種方式組織的一塊內(nèi)存區(qū)域。PUSH指令添加一個數(shù)據(jù)到堆棧中而POP指令從堆棧中移除數(shù)據(jù)。移除的數(shù)據(jù)就是最后入棧的數(shù)據(jù)(這就是稱為先進后
出隊列的緣故)。
SS段寄存器指定包含堆棧的段(通常它與儲存數(shù)據(jù)的段是一樣)。ESP寄存器包含將要移除出棧數(shù)據(jù)的地址。這個數(shù)據(jù)也被稱為棧頂。數(shù)據(jù)只能以雙字的形式入棧。也就是說,你不可以將一個字節(jié)推入棧中。
PUSH指令通過把ESP減4來向堆棧中插入一個雙字,然后把雙字儲存到[ESP]中。POP指令從[ESP]中讀取雙字,然后再把ESP加4.下面的代碼演示了這些指令如何工作,假定在ESP初始值為1000H。
堆??梢苑奖愕赜脕砼R時儲存數(shù)據(jù)。它同樣可以用來形成子程序調(diào)用和傳遞參數(shù)和局部變量。
80x86同樣提供一條PUSHA指令來把EAX,EBX,ECX,EDX,ESI,EDI和EBP寄存器的值推入棧中(不是以這個順序)。POPA指令可以用來將它們移除出棧。
更多建議: