1. 內(nèi)存中字的存儲

數(shù)字:20000,十六進(jìn)制:4E20H。
4E20 一共 16 位,我們稱之為一個(gè)字。
CPU中,使用 16 位寄存器存儲一個(gè)字。并用高 8 位寄存器來存放高位字節(jié)(4E),低 8 位寄存器存放低位字節(jié)(20)。如圖 4-2 ,使用 寄存器 AX 存放4E20H, 圖 4-1 右側(cè)顯示了寄存器低位和高位存儲情況

如圖 4-1 左側(cè),在內(nèi)存中,每個(gè)內(nèi)存單元是字節(jié)單元(一個(gè)單元存儲一個(gè)字, 8 位),那么一個(gè)字要兩個(gè)地址連續(xù)的內(nèi)存單元來存放。同理,低地址單元存放低位字節(jié)(20),高地址單元存放高位字節(jié)(4E)。

如圖 4-3 黃色區(qū)域中的的兩個(gè)連續(xù)的內(nèi)存單元組成了一個(gè)字單元。高地址內(nèi)存單元存放字型數(shù)據(jù)的高位字節(jié),低地址內(nèi)存單元存放字型數(shù)據(jù)的低位字節(jié)。
問題分析:
對于圖 4-1:
1)0 地址單元中存放的字節(jié)型數(shù)據(jù)是多少?
2)0 地址字單元中存放的字型數(shù)據(jù)是多少?
3)2 地址單元中存放的字節(jié)型數(shù)據(jù)是多少?
4)2 地址單元中存放的字型數(shù)據(jù)是多少?
5)1 地址子單元中存放的字型數(shù)據(jù)是多少?
分析:
1)12H
2) 4E12H
3) 12H
4) 0012H
- 124EH
任何兩個(gè)地址連續(xù)的內(nèi)存單元, N 號單元和 N+1 號單元,可以將它們看成兩個(gè)內(nèi)存單元,也可以看成一個(gè)地址為 N 的字單元的高位字節(jié)單元和低位字節(jié)單元
2. DS 和 [address]
CPU 要讀寫一個(gè)內(nèi)存單元中的數(shù)據(jù),就必須先給出這個(gè)內(nèi)存單元的第一種,而 8086CPU 內(nèi)存地址有段地址和偏移地址組成。 在 8086CPU 中有個(gè)寄存器 DS,這個(gè)寄存器就是用來存放要訪問的內(nèi)存單元的段地址。
比如,我們要讀取 10000H 單元的內(nèi)容,可以用如下的程序段進(jìn)行:
mov bx, 1000
mov ds, bx
mov al,[0]
上面的三條命令將 10000H(1000:0)中的數(shù)據(jù)讀到 al 中。如圖演示:

如上圖演示,最終 ax 寄存器中存放了 0012H 也就是內(nèi)存地址 10000H 中的數(shù)據(jù)。
對于 mov 指令功能:
① 將數(shù)據(jù)直接送入寄存器內(nèi) (mov bx,1000,將 1000H 送入寄存器 bx 中)
② 將一個(gè)寄存器中的內(nèi)容送入另一個(gè)寄存器 (mov ds,bx 將 bx 內(nèi)容送入到 ds 中)
除此之外,也可以通過 mov 指令將一個(gè)內(nèi)存單元中的內(nèi)容送入到一個(gè)寄存器中。
mov ax, [0]
上面這條命令中 [0] 表示的是偏移地址,但是僅僅有偏移地址是不能定位一個(gè)內(nèi)存單元的,對于 8086CPU 而言會自動(dòng)從去取 DS 寄存器中的數(shù)組作為內(nèi)存單元的段地址。
再來看一下,如何用 mov 指令從 10000H 中讀取數(shù)據(jù):
- 10000H 用段地址 和 偏移地址表示為: 1000:0
- 通過 mov bx,1000 mov ds,bx 將段地址 1000H送入寄存器 ds 中
- 然后通過 mov al,[0] 將數(shù)據(jù)從內(nèi)存單元 1000:0 取出并送入寄存器al(ax低位寄存器)中
這里可能看出一個(gè)問題,就是第 2 步的時(shí)候,為什么不直接通過 mov ds,1000 將 1000H 直接送入寄存器: DS ?
這屬于 8086CPU 硬件設(shè)計(jì)問題,DS 是一個(gè)段寄存器,8086CPU 不支持直接將數(shù)據(jù)送入段寄存器的操作。
問題:寫幾條指令將 al 中的數(shù)據(jù)送入內(nèi)存單元 10000H 中(具體操作見下面的gif):
mov bx,1000
mov ds,bx
mov [0],al

3. 字的傳送
上面通過 mov 指令在寄存器和內(nèi)存之間進(jìn)行字節(jié)型數(shù)據(jù)的傳送。因?yàn)?8086CPU 是 16 位結(jié)構(gòu),有 16 根數(shù)據(jù)線,所以, 可以一次性傳送 16 位的數(shù)據(jù),也就是說可以一次性傳送一個(gè)字。只要在 mov 指令中給出 16 位的寄存器就可以進(jìn)行 16 位數(shù)據(jù)的傳送了。
指令樣板如下:
mov bx, 1000
mov ds,bx
mov ax,[0]
mov [0],cx

問題 4-3:
內(nèi)存情況如圖 4-4 所示:

執(zhí)行如下命令后寄存器 ax, bx, cx 值各是多少?
mov ax,1000
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1]
add bx,[1]
add cx,[2]
分析:
| 指令 | 執(zhí)行后寄存器內(nèi)容 | 指令說明 |
|---|---|---|
| mov ax,1000 | ax=1000H | 向寄存器 ax 中送入內(nèi)容 1000 |
| mov ds,ax | ds=1000H | 段地址寄存器內(nèi)容設(shè)置為 1000 |
| mov ax,[0] | ax=1123H | 這里 ax 寄存器是 16 位,取內(nèi)存 10000 處 一個(gè)字型數(shù)據(jù) 1123H 送入 ax 中 |
| mov bx,[2] | bx=6622H | bx 16 位寄存器,取內(nèi)存 10002H 處一個(gè)字型數(shù)據(jù) 6622H 送入 bx 寄存器中 |
| mov cx,[1] | cx=2211H | cx 16 位寄存器,取內(nèi)存 10001H 處一個(gè)字型數(shù)據(jù) 2211H 送入 bx 寄存器中 |
| add bx,[1] | bx=8833H | bx 內(nèi)容為 6622H 取內(nèi)存 10001H 中 2211H 執(zhí)行 6622H + 2211H = 8833H,將計(jì)算結(jié)果送入 bx 寄存器中 |
| add cx,[2] | cx=8833H | cx 內(nèi)容 2211H 取出內(nèi)存 10002H 中 6622H 相加得出 8833H 送入 cx 寄存器中 |
具體操作:
通過 e 命令已經(jīng)將內(nèi)存數(shù)據(jù)寫入,然后向內(nèi)存中寫入上述指令,通過修改 cs:ip ,執(zhí)行指令

問題 4-4:
內(nèi)存情況如圖 4-5:

執(zhí)行下面命令后內(nèi)存中的值:
mov ax,1000
mov ds,ax
mov ax,11316(十進(jìn)制)
mov [0],ax
mov bx,[0]
sub bx,[2]
mov [2],bx
| 指令 | 執(zhí)行后寄存器內(nèi)容 | 指令說明 |
|---|---|---|
| mov ax,1000 | ax=1000H | 向寄存器 ax 中送入內(nèi)容 1000 |
| mov ds,ax | ds=1000H | 段地址寄存器內(nèi)容設(shè)置為 1000 |
| mov ax,11316 | ax=2C34H | 11316 的十六進(jìn)制 2C34H |
| mov [0],ax | 內(nèi)存 10000H:34 10001H:2C | ax 中低位送入 10000H,高位送入 10001H |
| mov bx,[0] | bx=2C34 | 取出內(nèi)存 10000H 出字型數(shù)據(jù) 2C34 放入 bx 寄存器 |
| sub bx,[2] | bx=1B12 | 2C34H - 1122H=1B12H |
| mov [2],bx | 10003H:1B 10002:12 其他不變 | 寄存器 bx 中 1B12H 送入內(nèi)存 |
4 mov add sub 指令
前面我們用到的 mov 指令形式有以下幾種:
mov 寄存器,數(shù)據(jù)
mov 寄存器,寄存器
mov 寄存器,內(nèi)存單元
mov 內(nèi)存單元,寄存器
mov 段寄存器,寄存器
基于前面使用的指令,猜想:
1) mov 寄存器,段寄存器(驗(yàn)證寄存器和段寄存器之間是否有相反的通路)

通過上面執(zhí)行 mov ax,ds 可以看到詞條命令是合法的, 所以寄存器和段寄存器之間是相互通路的。
2)mov 內(nèi)存單元,段寄存器
mov ax,1000H
mov ds,ax
mov [0],cs
操作如下:CS 中內(nèi)容 073F,將其放入的 10000H 內(nèi)存單元內(nèi),這里需要注意的是 CS 是一個(gè) 16 位寄存器,所以需要兩個(gè)內(nèi)存單元 存放其中去除的數(shù)據(jù),低地址存放 3F 高地址存放 07:

3)mov 段寄存器,內(nèi)存單元
mov ax, 1000H
mov ds,ax
mov ds,[0]
將內(nèi)存單元中 10000H 字型數(shù)據(jù)送入 DS 寄存器中:

下面這些以供練習(xí):
add 寄存器, 數(shù)據(jù)
add 寄存器,寄存器
add 寄存器,內(nèi)存單元
add 內(nèi)存單元,寄存器
sub 寄存器,數(shù)據(jù)
sub 寄存器,寄存器
sub 寄存器,內(nèi)存單元
sub 內(nèi)存單元,寄存器
5 數(shù)據(jù)段
對于 8086PC 機(jī),在編程時(shí),可以根據(jù)需要,將一組內(nèi)存單元定義為一個(gè)端。我們可以將一組產(chǎn)孤單為N(N <= 64KB) 地址連續(xù),起始地址為 16 的倍數(shù)的內(nèi)存單元當(dāng)作專門存儲數(shù)據(jù)的內(nèi)存空間,從而定義了一個(gè)數(shù)據(jù)段。
比如 123B0H ~ 123B9H 這段內(nèi)存空間來存放數(shù)據(jù),我們就可以認(rèn)為, 123B0H ~ 123B9H 這段內(nèi)存是一個(gè)數(shù)據(jù)段,它的段地址為 123BH,長度為 10 個(gè)字節(jié)。
那么,如何訪問數(shù)據(jù)段中的數(shù)據(jù)呢?
將一段內(nèi)存當(dāng)作數(shù)據(jù)段,是我們在編程時(shí)的一種安排,可以在具體操作的時(shí)候,用 ds 寄存器存放數(shù)據(jù)段的段地址,再根據(jù)需要,用相關(guān)指令訪問數(shù)據(jù)段中的具體單元。
比如,將 123B0H ~ 123B9H 的內(nèi)存單元定義為數(shù)據(jù)段。現(xiàn)在要累加這個(gè)數(shù)據(jù)段中的前 3 個(gè)單元中的數(shù)據(jù),代碼如下:
mov ax, 123bH
mov ds,ax
mov al,0
add al,[0]
add al,[1]
add al,[2]
這里需要強(qiáng)調(diào)一點(diǎn),是前3 單元內(nèi)容,內(nèi)存單元每個(gè)大小為 8 位(即一個(gè)字節(jié)),所以只需要的是 8 位寄存器,當(dāng)然這里可以自行驗(yàn)證,當(dāng)?shù)匚患拇嫫鲾?shù)據(jù)超過了之后是否會進(jìn)位到高位寄存器。

問題 4-5
寫幾條指令,累加數(shù)據(jù)段中前 3 個(gè)字型數(shù)據(jù):
mov ax,123BH
mov ds,ax
mov ax,0
mov ax,[0]
mov ax,[2]
mov ax,[4]
小結(jié):
1) 字在內(nèi)存中存儲時(shí),要用兩個(gè)地址連續(xù)的內(nèi)存單元來存放,字的低位字節(jié)存放在低地址單元中,高位字節(jié)存放在高地址單元中;
2)用 mov 指令訪問內(nèi)存單元,可以再 mov 指令中只給出單元的偏移地址,此時(shí),段地址默認(rèn)在 DS 寄存器中;
3)[address] 表示一個(gè)偏移地址為 address 的內(nèi)存單元;
4)在內(nèi)存和寄存器之間傳送字型數(shù)據(jù)時(shí),高地址單元和高 8 位寄存器、低地址單元和低 8 位寄存器相互對應(yīng);
5)mov、add、sub 是具有兩個(gè)操作對象的指令。 jmp 是具有一個(gè)操作對象的指令;
6)可以根據(jù)自己的推測,在 Debug 中實(shí)驗(yàn)指令的新格式
6 棧
這了對棧的研究限于:棧是一種具有特殊訪問方式的存儲空間(LIFO)。
這里通過盒子放書的操作過程來描述棧的操作,如圖 4-6 所示:

現(xiàn)在,一次只允許取一本,如何將 3 本書從盒子中取出?
顯然,必須從盒子最上邊取,這樣取出的順序就是: 《軟件工程》《C 語言》《高等數(shù)學(xué)》,和放入的順序正好相反,如圖 4-7 所示:

從程序化角度,我們通過綠色箭頭做一個(gè)標(biāo)記,這個(gè)標(biāo)記一直指著盒子最頂端的書籍。
總結(jié):
如果說盒子就是一個(gè)棧,那么這個(gè)棧有兩個(gè)操作,分別是入棧和出棧。
入棧,就是將一個(gè)新的元素放到棧頂,出棧就是從棧頂取出一個(gè)元素。棧頂?shù)脑乜偸亲詈笕霔?,如果出棧的話,棧頂元素也是第一個(gè)被取出(LIFO)。
7 CPU 提供的棧機(jī)制
現(xiàn)在的 CPU 中都有棧的設(shè)計(jì), 8086CPU 也不例外。8086CPU 提供相關(guān)的指令來以棧的方式訪問內(nèi)存空間。這意味著,在基于 8086CPU 編程的時(shí)候,可以將一段內(nèi)存當(dāng)做棧來使用。
8086CPU 提供入棧和出棧指令,最基本的兩個(gè)是 PUSH 和 POP ,比如, push ax 表示將寄存器 ax 中的數(shù)據(jù)送入到棧中, pop ax 表示從棧頂取出數(shù)據(jù)送入到 ax。 8086CPU 的入棧和出棧操作都是以字為單位進(jìn)行。
舉例說明,我們可以將 10000H~1000FH這段內(nèi)存當(dāng)做棧來使用,如圖 4-8 所示:

指令如下:
mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax
pop bx
pop cx
注意,字型數(shù)據(jù)用兩個(gè)單元存放,高地址單元存放高 8 位,低地址單元存放低 8 位。
如圖 4-8 所示,有兩個(gè)疑惑在這里說明一下:
第一點(diǎn)疑惑,上面我們將內(nèi)存的 10000H~1000FH 這段作為棧來來使用,然后執(zhí)行 push 和 pop 指令。但是,CPU 是如何知道 10000H~1000FH 這段空間被當(dāng)作棧來使用的呢 ?
第二點(diǎn)疑惑,push ax , pop ax 等指令執(zhí)行的時(shí)候,要訪問棧頂單元,那么這兩個(gè)指令又是如何知道那個(gè)內(nèi)存單元是棧頂單元呢 ?
這里,我們回顧下,CPU 如何知道當(dāng)前要執(zhí)行指令所在的位置呢 ?
在前面的操作中,我們也已經(jīng)知道,那就是 CS 和 IP 兩個(gè)寄存器存放著當(dāng)前指令的段地址和偏移地址。

現(xiàn)在的問題是: CPU 如何知道棧頂位置 ?
顯然,也應(yīng)該有相應(yīng)的寄存器來存放棧頂?shù)刂罚?806CPU 中,有兩個(gè)寄存器,段寄存器 SS 和 SP,棧頂段地址存放在 SS 中,偏移地址存放在 SP 中。
在任意時(shí)候, SP:IP指向棧頂元素
push 和 pop 指令執(zhí)行時(shí),CPU 從 SS 和 SP 中得到棧頂?shù)牡刂贰?br> 現(xiàn)在,我們可以完整地描述 push 和 pop 指令的功能了,例如 push ax。
push ax 的執(zhí)行,由一下兩步完成:
(1)SP=SP-2, SS:SP 指向當(dāng)前棧頂前面的單元,以當(dāng)前棧頂前面的單元為新的棧頂;
(2)將 ax 中的內(nèi)容送入 SS:SP 指向的內(nèi)存單元處,SS:SP此時(shí)指向新棧頂。
如圖 4-8 所示:

從圖中,可以看出,8086CPU中入棧時(shí),棧頂從高地址向低地址方向增長。
問題 4-6
如果將 10000H ~ 1000FH 這段地址當(dāng)做棧,初始狀態(tài)是空棧,此時(shí) SS = 1000H,那么 SP=?
分析

SP = 0010H,如圖 4-8 所示,
將 10000H ~ 1000FH 這段空間當(dāng)做棧段,SS = 1000H,棧空間大小為 16 字節(jié),棧最底部的字單元地址為 1000:000E。 任意時(shí)刻,SS:SP 指向棧頂,當(dāng)棧中只有一個(gè)元素的時(shí)候,SS=10000H, SP=1000EH。
棧為空的話,也就是說,相當(dāng)于棧中唯一的元素出棧,出棧后,SP=SP+2,SP 原來為 000EH,加 2 后,SP = 10H,所以當(dāng)??盏臅r(shí)候,SS=1000H,SP=10H。
換一個(gè)角度看,任意時(shí)刻,SS:SP 指向棧頂元素,當(dāng)棧為空的時(shí)候,棧中沒有元素,也就不存在棧頂元素,所以 SS:SP 指向棧最底部單元下面的單元,該單元的偏移地址為最底部字單元偏移地址 +2 ,棧最底部的字單元地址為 1000:000E, 所以??諘r(shí), SP=0010H。
接下來,看一下 pop 操作:
pop ax
pop ax 和 push ax 正好相反,由以下兩步組成:
(1)將 SS:SP 指向的內(nèi)存單元處的數(shù)據(jù)送入 ax 中;
(2)SP=SP+2,SS:SP指向當(dāng)前棧頂下面的一個(gè)單元,以當(dāng)前棧頂下面的單元為新的棧頂。
如下圖 4-10 所示, POP AX 的過程:

這里需要注意第三步,盡管當(dāng)前棧指針指向 1000E H 但是內(nèi)存單元 1000DH 和 1000CH 中的數(shù)據(jù)任然存在,只不過不在棧中,直到下一次 PUSH 操作將其覆蓋為止。
8 棧頂越界問題
這一小節(jié),介紹越界問題。
比如,我們將內(nèi)存地址空間 10010H ~ 1001FH 作為??臻g,該??臻g容量為 16 字節(jié)(8個(gè)字)。

初始狀態(tài)棧為空, SS=1000H,SP=0020H,SS:SP 指向 10020H。

ax=0123H

執(zhí)行 8 次 PUSH AX 操作,向棧中壓入 8 個(gè)字,棧滿,SS=1000H SP=0010, SS:SP 指向 10010H


執(zhí)行 8 次 PUSH AX 操作之后,棧滿,如果在執(zhí)行一次 PUSH AX,那么 SP=SP-2,SP=000E, SP=1000E

此時(shí),PUSH 操作已經(jīng)超過了??臻g,將棧外空間數(shù)據(jù)覆蓋。
POP 操作類似。
主要操作細(xì)節(jié)是:
通過 -a 將命令寫入當(dāng)前指令寄存器指向的內(nèi)存地址空間,然后,通過 -r 指令修改寄存器內(nèi)容,包括棧寄存器 SP 和 SS 以及數(shù)據(jù)寄存器 AX 內(nèi)容。
這里需要注意,POP 和 PUSH 都會出現(xiàn)越界,但是 8086CPU 并沒有機(jī)制保證我們不越界,所以在編程的時(shí)候需要自己操作棧頂越界的問題。
9 push 、pop 指令
前面我們一直在使用 PUSH AX 和 POP AX 這兩條指令,他們可以將數(shù)據(jù)在內(nèi)存和寄存器之間進(jìn)行傳送。(??臻g是內(nèi)存的一部分,它只是一段可以以一種特殊方式進(jìn)行訪問的內(nèi)存空間)
除此之外, PUSH 和 POP 這兩條指令的目標(biāo)地址還可以是段寄存器或者內(nèi)存空間。
練習(xí) 1
將 10000H~1000F 這段空間當(dāng)做棧,初始狀態(tài)??眨瑢?ax,bx,ds 中的數(shù)據(jù)送入棧。
-
設(shè)置棧寄存器
SS=1000H SP=0010 SS:SP=10010
-
將數(shù)據(jù)送入寄存器
ax=1234H
bx=5678H
ds=9ABCH
-
將指令 push ax ,push bx, push ds 寫入內(nèi)存單元
-
執(zhí)行第 3 步寫入的指令
執(zhí)行 PUSH 操作

練習(xí) 2
編程:
(1) 將 10000H~1000FH 這段空間當(dāng)作棧,初始狀態(tài)是空的;
(2)設(shè)置 AX=001AH , BX=001BH;
(3)將 AX、BX 中的數(shù)據(jù)入棧;
(4)然后將 AX、BX 清零;
(5)從棧中恢復(fù) AX、BX 原來的內(nèi)容。
解析:
首先設(shè)置棧寄存器值
SS=1000H SP=0010H SS:SP=10010H設(shè)置 AX BX 的值
AX=001AH
BX=001BH入棧,執(zhí)行 PUSH AX 和 PUSH BX 操作
a 寫入 push ax 和 push bx
t 執(zhí)行命令清零
ax=0000H
bx=0000H執(zhí)行 POP BX POP AX 操作



