从访问内存的角度继续学习寄存器;
在0地址处开始存放20000(4E20H):
注意:0号单元是低地址单元,1号单元是高地址单元。
问题: (1)0地址单元中存放的字节型数据是多少? 20H (2)0地址字单元中存放的字型数据是多少? 4E20H (3)2地址单元中存放的字节型数据是多少? 12H (4)2地址字单元中存放的字型数据是多少? 0012H (5)1地址字单元中存放的字型数据是多少? 124EH
结论:
任何两个地址连续的内存单元,N 号单元和 N+1号单元,可以将它们看成两个内存单元 ,也可以看成一个地址为 N 的字单元中的高位字节单元和低位字节单元。
CPU 要读取一个内存单元的时候,必须先给出这个内存单元的地址;在8086PC 中,内存地址由段地址和偏移地址组成。
8086CPU 中有一个 DS 寄存器,通常用来存放要访问的数据的段地址。
例如:我们要读取10000H单元的内容可以用如下程序段进行:
mov bx,1000H
mov ds,bx
mov al,[0]
上面三条指令将10000H(1000:0)中的数据读到al中。
执行指令时,8086CPU 自动取 DS 中的数据为内存单元的段地址。
如何用 mov 指令从10000H中读取数据?
10000H表示为1000:0(段地址:偏移地址),将段地址1000H放入 ds,用 mov al,[0]
完成传送(mov 指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在 ds 中);
如何把1000H送入 ds?
传送指令 mov ax,1
,mov ds,ax
;
mov ds,1000H
是不行的,8086CPU 不支持将数据直接送入段寄存器的操作,ds 是一个段寄存器(硬件设计的问题),所以 mov ds,1000H
是非法的。
数据 → 通用寄存器 → 段寄存器;
问题:
写几条指令,将 al 中的数据送入内存单元10000H?
mov bx,1000H
mov ds,bx
mov [0],al
因为 8086CPU 是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是一次性传送一个字。
比如:
问题1:
存中的情况如下图,写出下面指令执行后寄存器ax,bx,cx中的值。
ax = 1123H
bx = 6622H
cx = 2211H
bx = 8833H
cx = 8833H
问题2:
内存中的情况如下图,写出下面指令执行后寄存器ax,bx,cx中的值。
ax = 11316 = 2c34H
bx = 2c34H
bx = 1b12H
已学mov指令的几种形式:
mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 段寄存器,寄存器
根据已知指令进行推测:
mov 段寄存器,寄存器
→ mov 寄存器,段寄存器
验证:
mov 内存单元,寄存器
→ mov 内存单元,段寄存器
→ mov 段寄存器,内存单元
add 和 sub 指令同 mov 一样,都有两个操作对象。
前面讲过,对于 8086PC 机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等)。
我们可以将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
比如我们用123B0H~123B9H这段空间来存放数据:
如何访问数据段中的数据呢?
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
示范
我们将123B0H~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,代码如下:
(1)字在内存中存储时 ,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
(2)用 mov 指令要访问内存单元,可以在 mov 指令中只给出单元的偏移地址,此时,段地址默认在 DS 寄存器中。
(3)[address] 表示一个偏移地址为 address 的内存单元。
(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
(5)mov,add,sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令。
(6)可以根据自己的推测,在 Debug 中实验指令的新格式。
(1) 在DEBUG中,用 d 0:0 1f
查看内存,结果如下:
0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88
下面的程序执行前,AX=0
,BX=0
,写出每条汇编指令执行完后相关寄存器中的值:
mov ax,1
mov ds,ax
mov ax,[0000] ax= 2662H
mov bx,[0001] bx= E626H
mov ax,bx ax= E626H
mov ax,[0000] ax= 2662H
mov bx,[0002] bx= D6E6H
add ax,bx ax= FD48H
add ax,[0004] ax= 2C14H
mov ax,0 ax= 0
mov al,[0002] ax= 00e6H
mov bx,0 bx= 0
mov bl,[000c] bx= 0026H
add al,bl ax= 000CH
(2) 内存中的情况如图所示
各寄存器的初始值:cs=2000h,ip=0,ds=1000h,ax=0,bx=0;
① 写出CPU执行的指令序列(用汇编指令写出)。
② 写出CPU执行每条指令后,CS、IP和相关寄存器的数值。
③ 再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?
CS=2000H,IP=0,DS=1000H
AX=0,BX=0
mov ax,6622H
CS=2000H IP=3 DS=1000H AX=6622H BX=0000H
jmp 0ff0:0100
CS=1000H IP=0 DS=1000H AX=6622H BX=0000H
mov ax,2000H
CS=1000H IP=3 DS=1000H AX=2000H BX=0000H
mov ds,ax
CS=1000H IP=5 DS=2000H AX=2000H BX=0000H
mov ax,[0008]
CS=1000H IP=8 DS=2000H AX=C389H BX=0000H
mov ax,[0002]
CS=1000H IP=000B DS=2000H AX=EA66H BX=0000H
数据和程序在计算机中都是以二进制的形式存放的,在区别程序和数据时,关键是看段地址,如果段地址是 ds 段,说明该内存存放的是数据,如果段地址是 cs 段,说明该内存存放的是指令。
栈是一种具有特殊的访问方式的存储空间,它的特殊性就在于,最后进入这个空间的数据,最先出去。
栈有两个基本的操作:入栈和出栈。
栈的操作规则:LIFO(Last In First Out,后进先出)
现今的 CPU 中都有栈的设计。8086CPU 提供相关的指令来以栈的方式访问内存空间。这意味着,我们在基 于8086CPU 编程的时候,可以将一段内存当作栈来使用。
8086CPU 提供入栈和出栈指令(最基本的):
push ax
:将寄存器 ax 中的数据送入栈中; pop ax
:从栈顶取出数据送入 ax。
8086CPU的入栈和出栈操作都是以字为单位进行的。
注意:字型数据用两个单元存放,高地址单元放高 8 位,低地址单元放低8 位。
问:CPU 如何知道一段内存空间被当作栈使用?执行 push 和 pop 的时候,如何知道哪个单元是栈顶单元?
结论:任意时刻,SS:SP 指向栈顶元素。
push ax
:
(1)SP=SP–2
; (2)将 ax 中的内容送入 SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶。
问:如果我们将10000H~1000FH 这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?
答:SP = 0010H
pop ax
:
(1)将 SS:SP 指向的内存单元处的数据送入 ax 中; (2)SP = SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
注意:
例题
(1)将10000H~1000FH 这段空间当作栈,初始状态是空的; (2)设置AX=001AH,BX=001BH; (3)将AX、BX中的数据入栈; (4)然后将AX、BX清零; (5)从栈中恢复AX、BX原来的内容。
结论:
push,pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与 mov 指令不同的是,push 和 pop 指令访问的内存单元的地址不是在指令中给出的,而是由 SS:SP 指出的。
同时,push 和 pop 指令还要改变 SP 中的内容:
栈顶的变化范围最大为:0~FFFFH。
SS 和 SP 只记录了栈顶的地址,依靠 SS 和 SP 可以保证在入栈和出栈时找到栈顶。
可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?
当栈满的时候再使用 push 指令入栈,栈空的时候再使用 pop 指令出栈,都将发生栈顶超界问题。栈顶超界是危险的。
因为我们既然将一段空间安排为栈 ,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。(毕竟一个计算机系统并不是只有我们自己的程序在运行)
但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。如果是刻意的话,就会显得很糟糕了。
8086CPU 不保证对栈的操作不会超界。这就是说, 8086CPU 只知道栈顶在何处(由SS:SP指示),而不知道我们安排的栈空间有多大。这点就好像 ,CPU 只知道当前要执行的指令在何处(由 CS:IP 指示)而不知道我们要执行的指令有多少。
8086CPU的工作机理,只考虑当前的情况:当前栈顶在何处;当前要执行的指令是哪一条。
结论:
(1)8086CPU 提供了栈操作机制,方案如下:
(2)push 指令的执行步骤:
SP = SP - 2
;(3)pop 指令的执行步骤:
SP=SP-2
;(4)任意时刻,SS:SP 指向栈顶元素。 (5)8086CPU 只记录栈顶,栈空间的大小我们要自己管理。 (6)用栈来暂存以后需要恢复的寄存器的内容时 ,寄存器出栈的顺序要和 入栈的顺序相反。 (7)push,pop 实质上是一种内存传送指令,注意它们的灵活应用。
栈是一种非常重要的机制,一定要深入理解,灵活掌握。
我们可以将长度为 N(N ≤64K )的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段。
比如我们将10010H~1001FH 这段长度为 16 字节的内存空间当作栈来用,以栈的方式进行访问。
这段空间就可以成为栈段,段地址为1000H,大小为16字节。
将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就在执行 push、pop 等栈操作指令时就自动地将我们定义的栈段当作栈空间来访问。
如何使的如 push、pop 等栈操作指令访问我们定义的栈段呢?
将 SS:SP 指向我们定义的栈段。
一个栈段最大可以设为多少?
首先从栈操作指令所完成的功能的角度上来看,push、pop 等指令在执行的时候只修改 SP;所以栈顶的变化范围是0~FFFFH,从栈空时候的 SP=0,一直压栈,直到栈满时 SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。
所以一个栈段的容量最大为64KB。
(1)补全下面的程序,使其可以将10000H-1000FH中的8个字,逆序拷贝到20000H-2000FH中。
mov ax,1000H
mov ds,ax
---------------------
mov ax,2000H |
mov ss,ax |
mov sp,10h |
---------------------
push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]
(2)补全下面的程序,使其可以将10000H-1000FH中的8个字,逆序拷贝到20000H-2000FH中。
mov ax,2000H
mov ds,ax
---------------------
mov ax,1000H |
mov ss,ax |
mov sp,0 |
---------------------
pop [e]
pop [c]
pop [a]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]