8086對記憶體定址的方式是通過段位址*16+偏移位址的方式實現的,而在16位的8086cpu下,段位址和偏移位址也都是16位的。這意味著,對於任意乙個段,段的起始位址必定為16的倍數(段位址*16)。
需要注意,記憶體段的概念並不是記憶體硬體所固有的,而是從cpu定址的角度出發,將記憶體中的物理連續區域邏輯上分隔為不同的區域。記憶體段這一概念的提出有利於在複雜程式、多程式系統中對同一程式的不同邏輯部分以及不同程式的記憶體進行更好的訪問和管理。
記憶體段的存在能夠劃分同一程式中的不同邏輯部分,進行更有效的管理和更簡單的訪問。匯程式設計序的記憶體通常可以被劃分為三部分:**、棧以及資料。
通常為了避免混淆,會將這三種不同的邏輯部分分別存放在三個不同的記憶體段中,便於理解(當然也能將同一部分的記憶體存放在不同的段中,這主要取決於程式的複雜程度)。
8086cpu提供了對**、棧、資料三種記憶體段訪問的段暫存器,分別是**段暫存器cs、棧段暫存器ss、資料段暫存器ds以及附加段暫存器es。
**段暫存器cs
**段暫存器cs在前面的部落格中已經有過介紹,cpu在執行時會將cs:ip指向的記憶體中的資料當作指令來執行。
棧段暫存器ss
執行棧相關的指令時,cpu通過ss:sp獲得當前棧頂指標。通過設定暫存器ss的值,可以將某一段記憶體當作邏輯上的棧來使用。有關棧的內容,會在部落格的後半段進行介紹。
資料段暫存器ds
但由於大多數時候程式中對記憶體的訪問都主要集中在某一邏輯段中,8086彙編提供了另一種更加簡單的記憶體定址方式:只需要指定偏移位址就能進行訪問。(例如:mov cx [2345h];其中2345h為偏移位址)
想要將位址12345h記憶體資料送入暫存器cx,[address]形式的指令序列為:mov ax 1000h; mov ds ax; mov cx [2345h]。
這裡之所以需要通過通用暫存器ax間接的將段位址1000h送入ds,是因為8086cpu不允許直接對段暫存器進行賦值,具體的原因可以參考:
附加段暫存器es
一段記憶體,既可以是程式的儲存空間,可以是資料的儲存空間,也可以作為棧空間來使用,甚至可以什麼都不是。這裡的關鍵在於cpu中相關暫存器cs/ip,ss/sp以及ds、es的值。
棧這個概念在計算機相關的領域內十分常見,其所具有的的後進先出的特性被很多程式廣泛利用。
在組合語言程式開發的過程中,在記憶體中資料的交換、子程式跳轉進行變數儲存等場景下,開發人員希望能夠將一段記憶體當作邏輯上後進先出的棧來使用,比起通過每次都指明特定的記憶體位址去操作儲存器,使用棧操作一段連續記憶體能讓程式更加簡單明瞭。
通過軟體來實現乙個棧,機器效率是十分低下的。為此,8086cpu的設計者提供了硬體層面的棧機制:通過特定的指令,cpu能夠像對待棧一樣來訪問指定的一段記憶體。
棧有兩種獨特的操作,一是入棧(push),二是出棧(pop),乙個棧只有棧頂指標指向的元素才可以被訪問。入棧和出棧時,棧頂指標也會相應的移動以指向新的棧頂處。
8086彙編提供了對應的指令push和pop來對記憶體進行入棧、出棧操作。同時8086cpu是16位的,因此pop和push所操作的棧中元素大小為兩個位元組。
push [暫存器],將指定暫存器中的值傳送到棧頂指標處,棧頂指標上移
push [記憶體單元],將指定記憶體單元處的值傳送到棧頂指標處,棧頂指標上移
pop [暫存器],將棧頂指標處的記憶體資料傳送到指定暫存器中,棧頂指標下移
pop [記憶體單元],將棧頂指標處的記憶體資料傳送到指定記憶體單元處,棧頂指標下移
可以這麼理解,push是入棧操作,那麼指令中的暫存器或是記憶體單元自然就是指向入棧資料所處的位置。而pop是出棧操作,指令中的暫存器或是記憶體單元則是用來承載出棧資料的。
隨之而來的問題是,cpu該如何知道在棧操作指令中沒有明確宣告的棧頂指標呢?
前面提到過,cpu是通過cs:ip來獲得當前所應該執行的指令;類似的,cpu通過另外兩個暫存器ss(stack-segment 棧段暫存器)和sp(stack-point 堆疊指標暫存器)來確定棧頂指標。在執行push/pop指令時ss:sp所指向的記憶體單元就是棧頂指標所在的位置,ss標識棧記憶體段,而sp標識記憶體段中棧頂指標的記憶體位址。8086的一段連續棧記憶體位址中,將高位記憶體視為棧底,低位視為棧頂。
push入棧:ss不變而sp自減2(空出兩個位元組共16位的記憶體空間),標識新的棧頂,隨後將資料送入棧頂
pop出棧:先將棧頂元素送入目的地(暫存器/記憶體),隨後ss不變而將sp自增2(縮小棧),標識新的棧頂
這裡只提到了棧的頂部指標,而沒有提到棧底指標。如果不斷的出棧導致sp指標越過棧的底部會怎樣?不斷的入棧導致sp越過了棧的頂部又會發生什麼呢?
組合語言給了程式設計師難以言喻的自由,將幾乎一切都交給了開發者自己控制,因此上述的棧訪問越界問題必須由程式設計師自己避免。
在出棧入棧過程中,棧段暫存器ss是不變的,這意味著8086能直接支援的最大棧就是64kb(段的最大值),而sp在極端情況下會發生棧的上溢或者下溢;或者乙個物理段被分成了許多的邏輯段,對棧頂指標的控制出現失誤,會導致不同邏輯段間的資料被覆蓋。
由於棧指標訪問越界造成的問題在程式設計時不易察覺,卻會帶來嚴重後果,需要加以小心。
8086組合語言學習 五 8086定址方式
前面的部落格都多少提到了8086彙編的記憶體定址,例如mov ax 2345h 而8086彙編還提供了更多 更靈活的定址方式,以滿足多種需求。需要強調的是,無論何種定址方式,記憶體定址的位址總是由基礎位址 偏移位址 段基址 偏移量 組合而成,不同之處在於基礎位址與偏移位址應該從何處獲得。在示例mov...
8086組合語言
cmc 進製位求反指令 stc 進製位置為1指令 cld 方向標誌置1指令 std 方向標誌位置1指令 cli 中斷標誌置0指令 sti 中斷標誌置1指令 nop 無操作 hlt 停機 wait 等待 esc 換碼 lock 封鎖 附上 藍色理想 的彙編學習心得位址 從今天開始溫習彙編了 1 基本概...
8086組合語言學習 九 8086標誌暫存器
前面已經介紹了8086大多數的暫存器,現在介紹一種8086內部乙個特殊的暫存器,標誌暫存器 flag register 8086標誌暫存器大致有以下作用 1.儲存一些相關指令的執行結果 2.為cpu執行相關指令提供依據 3.控制cpu的部分工作方式 8086的暫存器是16位的,通常的暫存器都是存放乙...