匯編基礎教程段的定義應用詳解

將一段內存定義為一個段,用一個段地址指示段,用偏移地址訪問段內的單元

種類

代碼段

定義

對於8086PC機,在編程時,可以根據需要,將一組內存單元定義為一個段。

可以將長度為 N( N≤64KB )的一組代碼,存在一組地址連續、起始地址為 16的倍數的內存單元中,這段內存是用來存放代碼的,從而定義瞭一個代碼段。

例如

這段長度為 10 字節的字節的指令,存在從123B0H~123B9H的一組內存單元中,我們就可以認為,123B0H~123B9H這段內存單元是用來存放代碼的 ,是一個代碼段 ,它的段地址為123BH,長度為10字節。

如何使得代碼段中的指令被執行

  • CPU 隻認被 CS:IP 指向的內存單元中的內容為指令。
  • 所以要將CS:IP指向所定義的代碼段中的第一條指令的首地址。
  • CS = 123BH,IP = 0000H。

數據段

定義

我們可以將一組長度為N(N≤64K)、地址連續、起始地址為16的倍數的內存單元當作專門存儲數據的內存空間,從而定義瞭一個數據段。

比如我們用123B0H~123B9H這段空間來存放數據:

  • 段地址:123BH
  • 長度:10字節

將一段內存當作數據段,是我們在編程時的一種安排,我們可以在具體操作的時候 ,用 ds 存放數據段的段地址,再根據需要,用相關指令訪問數據段中的具體單元。

DS和[address]

我們要讀取10000H單元的內容

CPU要讀取一個內存單元的時候,必須先給出這個內存單元的地址

  • 在8086PC中,內存地址由段地址和偏移地址組成。
  • 8086CPU中有一個 DS寄存器,通常用來存放要訪問的數據的段地址。

將10000H(1000:0)中的數據讀到al中。

mov bx,1000H

mov ds,bx

mov al,[0]

mov 指令可以將一個內存單元中的內容送入一個寄存器。

mov指令的格式:

  • mov 寄存器名,內存單元地址
  • “[…]”表示一個內存單元, “[…]”中的0表示內存單元的偏移地址。

如何用mov指令從10000H中讀取數據?

  • 10000H表示為1000:0(段地址:偏移地址)
  • 將段地址1000H放入ds
  • 用mov al,[0]完成傳送(mov指令中的[]說明操作對象是一個內存單元,[]中的0說明這個內存單元的偏移地址是0,它的段地址默認放在ds中)

如何把1000H送入ds?

  • 8086CPU不支持將數據直接送入段寄存器的操作,ds是一個段寄存器。(硬件設計的問題)
  • mov ds,1000H 是非法的。
  • 數據>>一般的寄存器>>段寄存器

例如

我們將123B0H~123BAH的內存單元定義為數據段,我們現在要累加這個數據段中的前3個單元中的數據,

代碼如下:

棧段

棧空間當然也是內存空間一部分,它隻是一段可以以一種特殊方式進行訪問的內存空間。

操作方式

棧有兩個基本的操作

入棧:將一個新的元素放到棧頂

出棧:從棧頂取出一個元素

棧頂的元素總是最後入棧,需要出棧時,又最先被從棧中取出

棧的操作規則:LIFO(Last In First Out,後進先出)

8086CPU的入棧和出棧操作都是以字為單位進行的。

註意:字型數據用兩個單元存放,高地址單元放高 8 位,低地址單元放低8 位。

8086CPU提供入棧和出棧指令

 SS:SP  

8086CPU中,有兩個寄存器:

  • 段寄存器SS  存放棧頂的段地址
  • 寄存器SP  存放棧頂的偏移地址

任意時刻,SS:SP指向棧頂元素。

SS和SP隻記錄瞭棧頂的地址,依靠SS和SP可以保證在入棧和出棧時找到棧頂。

棧頂超界的問題

種類

  • 當棧滿的時候再使用push指令入棧,
  • 棧空的時候再使用pop指令出棧,

8086CPU不保證對棧的操作不會超界。

8086CPU 隻知道棧頂在何處(由SS:SP指示),而不知道讀者安排的棧空間有多大。

8086CPU的工作機理,隻考慮當前的情況:

  • 當前棧頂在何處;
  • 當前要執行的指令是哪一條。

解決辦法

  • 要根據可能用到的最大棧空間,來安排棧的大小,防止入棧的數據太多而導致的超界
  • 執行出棧操作的時候也要註意,以防棧空的時候繼續出棧而導致的超界。

push、pop指令

定義

push和pop指令是可以在寄存器和內存之間傳送數據的。

PUSH(入棧)

push ax:將寄存器ax中的數據送入棧中;

執行過程 push ax

(1)SP=SP–2;

(2)將ax中的內容送入SS:SP指向的內存單元處,SS:SP此時指向新棧頂。

圖示

入棧時,棧頂從高地址向低地址方向增長

POP(出棧)

pop ax :從棧頂取出數據送入ax。執行過程 (1)將SS:SP指向的內存單元處的數據送入ax中;(2)SP = SP+2,SS:SP指向當前棧頂下面的單元,以當前棧頂下面的單元為新的棧頂。

圖示

 

註意:

出棧後,SS:SP指向新的棧頂 1000EH,pop操作前的棧頂元素,1000CH 處的2266H 依然存在 ,但是,它已不在棧中。

當再次執行push等入棧指令後,SS:SP移至1000CH,並在裡面寫入新的數據,它將被覆蓋。

格式

(1)

push 寄存器:將一個寄存器中的數據入棧

pop 寄存器:出棧,用一個寄存器接收出棧的數據

例如:push axpop bx

(2)

push 段寄存器:將一個段寄存器中的數據入棧 pop

段寄存器:出棧,用一個段寄存器接收出棧的數據

例如:push dspop es

(3)

push 內存單元:將一個內存單元處的字入棧(棧操作都是以字為單位)

pop 內存單元:出棧,用一個內存字單元接收出棧的數據

例如:push [0] pop [2]

指令執行時 ,CPU 要知道內存單元的地址,可以在 push、pop 指令中給出內存單元的偏移地址,段地址在指令執行時,CPU從ds中取得。

註意

與mov指令不同的是,push和pop指令訪問的內存單元的地址不是在指令中給出的,而是由SS:SP指出的。

CPU執行mov指令隻需一步操作,就是傳送,而執行push、pop指令卻需要兩步操作

執行push時:先改變SP,後向SS:SP處傳送。

執行pop時: 先讀取SS:SP處的數據,後改變SP。

push、pop 等棧操作指令,修改的隻是SP。也就是說,棧頂的變化范圍最大為:0~FFFFH。

提供:SS、SP指示棧頂;改變SP後寫內存的入棧指令;讀內存後改變SP的出棧指令。

棧段定義

  • 可以根據需要 ,將一組內存單元定義為一個段
  • 比如我們將10010H~1001FH 這段長度為 16 字節的內存空間當作棧來用,以棧的方式進行訪問。
  • 我們可以將長度為 N(N ≤64K )的一組地址連續、起始地址為16的倍數的內存單元,當作棧來用,從而定義瞭一個棧段
  • 這段空間就可以成為棧段,段地址為1000H,大小為16字節
  • 如何使的如push、pop 等棧操作指令訪問我們定義的棧段
  • 將SS:SP指向我們定義的棧段。

思考

我們將10000H~1FFFFH這段空間當作棧段 ,SS=1000H ,棧空間大小為64KB ,棧最底部的字單元地址為1000:FFFE。

任意時刻,SS:SP指向棧頂,當棧中隻有一個元素的時候,SS=1000H,SP=FFFEH。

棧為空,就相當於棧中唯一的元素出棧,出棧後,SP=SP+2。

SP原來為FFFEH,加2後SP=0,所以,當棧為空的時候,SS=1000H,SP=0。

任意時刻,SS:SP指向棧頂元素,當棧為空的時候 ,棧中沒有元素 ,也就不存在棧頂元素,所以SS:SP隻能指向棧的最底部單元下面的單元 ,該單元的偏移地址為棧最底部的字單元的偏移地址+2 ,棧最底部字單元的地址為1000:FFFE,所以棧空時,SP=0000H。

訪問

對於數據段,將它的段地址放在 DS中,用mov、add、sub等訪問內存單元的指令時,CPU就將我們定義的數據段中的內容當作數據段來訪問;

對於代碼段,將它的段地址放在 CS中,將段中第一條指令的偏移地址放在IP中,這樣CPU就將執行我們定義的代碼段中的指令;

對於棧段,將它的段地址放在SS中,將棧頂單元的偏移地置放在 SP 中,這樣CPU在需要進行棧操作的時候,比如執行 push、pop 指令等,就將我們定義的棧段當作棧空間來用。

可見,不管我們如何安排 ,CPU 將內存中的某段內存當作代碼 ,是因為CS:IP指向瞭那裡;CPU將某段內存當作棧 ,是因為 SS:IP 指向瞭那裡

比如我們將10000H~1001FH安排為代碼段,並在裡面存儲如下代碼:

設置CS=1000H,IP=0,這段代碼將得到執行。

可以看到,在這段代碼中,我們又將10000H~1001FH 安排為棧段和數據段。

10000H~1001FH這段內存,既是代碼段,又是棧段和數據段。

一段內存,可以既是代碼的存儲空間,又是數據的存儲空間,還可以是棧空間,也可以什麼也不是。

關鍵在於CPU中寄存器的設置,即:CS、IP、SS、SP、DS的指向。

段前綴

在訪問內存單元的指令中,用於顯式地指明內存單元的段地址的“ds:”、“cs:”、“ss:”或“es:”

應用

場景1

問題:將內存ffff:0~ffff:b段元中的數據拷貝到 0:200~0:20b單元中。

分析

(1) 0:200~0:20b單元等同於0020:0~0020:b單元,它們描述的是同一段內存空間

(2)拷貝的過程應用循環實現,簡要描述如下:

  • 初始化:X=0 循
  • 環12次

將ffff:X單元中的數據送入0020:X(需要用一個寄存器中轉)

X=X+1

(3)在循環中,源單元ffff:X和目標單元的0020:X的偏移地址X是變量。我們用bx來存放

(4)我們用將0:200~0:20b用0020:0~0020:b描述,就是為瞭使目標單元的偏移地址和源始單元的偏移地址從同一數值0開始。

問題

因源單元ffff:X和目標單元0020:X 相距大於64KB,在不同的64KB段裡,程序中,每次循環要設置兩次ds。

這樣做是正確的,但是效率不高。

解決

我們可以使用兩個段寄存器分別存放源單元ffff:X和目標單元0020:X的段地址,這樣就可以省略循環中需要重復做12次的設置ds的程序段。

改進的程序中,使用 es 存放目標空間0020:0~0020:b的段地址,用ds存放源空間ffff:0~ffff:b的段地址。

在訪問內存單元的指令“mov es:[bx],al”中 ,顯式地用段前綴 “es:” 給出單元的段地址,這樣就不必在循環中重復設置ds

場景2

Debug和匯編編譯器Masm對指令的不同處理

Debug中

  • mov ax,[0]
  • 表示將ds:0處的數據送入al中。

在匯編源程序中,指令“mov ax,[0]”被編譯器當作指令“mov ax,0”處理。

解決:利用段前綴顯性的指出段地址

以上就是匯編基礎教程段的定義應用詳解的詳細內容,更多關於匯編基礎教程段的資料請關註WalkonNet其它相關文章!

推薦閱讀: