CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,比如我们从0地址开始存放20000(4E20H),如图所示:
0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两字节单元构成)0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在1号单元中。
我们提出字单元的概念:字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址存放字型数据的高位字节,低地址内存单元存放字型数据的低位字节。
之后我们将起始地址为N的字单元称为N地址字单元。比如一个字单元是由2、3两个内存单元构成,起始地址是2,我们说是2地址字单元。
CPU中要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存单元的地址由段地址和偏移地址构成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取10000H单元中的内容,可以用如下程序段进行:
mov bx, 1000H
mov ds, bx
mov al, [0]
上面的3条指令将10000H(1000:0)的数据读到al中。
下面我们说明指令mov al, [0]
的含义
前面我们使用mov指令,可以完成两种传送:
也可以使用mov指令将一个内存单元中的内容送入另一个寄存器中。从哪一个内存单元送到哪一个寄存器中?在指令中必须指明。寄存器用寄存器名来指明,内存单元则需要用内存单元的地址来指明。显然,此时mov指令的格式应该是:mov 寄存器名,内存单元地址
[…]表示一个内存单元,[0]中的0表示内存单元的偏移地址,但是只有偏移地址是不能定位一个内存单元的,那么如何确认段地址?指令执行时,8086CPU自动取ds中的数据为内存单元的段地址。
再来看,10000H用段地址和偏移地址表示1000:0,我们先将段地址1000H放入ds,然后用mov al,[0]完成传送,mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中,指令执行时,8086CPU会自动从ds中取出。
8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以mov ds,1000H这种指令是非法的。如何将1000H送入ds?只好用一个寄存器进行中转,如bx,再将bx中的内容送入ds
从内存单元到寄存器的格式是:mov 寄存器名,内存单元地址
从寄存器到内存单元则是:mov 内存单元地址,寄存器名
mov bx, 1000H
mov ds, bx
mov ax, [0] ;1000:0处的字型数据送入ax
mov [0], cx ;cs中16位数据送到1000:0处
(a) 既然有mov 段寄存器, 寄存器
,从寄存器向段寄存器传送数据,那么也应该有mov 寄存器, 段寄存器
,从段寄存器向寄存器传送数据,有了推测,我们在debug下进行验证:
发现是正确的,所以mov 寄存器, 段寄存器
是正确的指令
(b) 既然有mov 内存单元,寄存器
,从寄存器向内存单元传送数据,那么也应该有mov 内存单元,段寄存器
,从段寄存器向内存单元传送数据。我们可以将段寄存器cs中的内容送入内存10000H处。
mov ax, 1000H
mov ds, ax
mov [0], cs
在Debug中进行试验:
mov [0], cs
执行后,cs中的内容写入1000:0内存单元中,如图:
高位字节是07H,低位字节是3FH
对于8086PC机,可以将一组内存单元定义为一个段,我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元专门当做存储数据的内存空间,从而就定义了一个数据段
如何访问数据段中的数据?将一段内存当做数据段,是我们的一种安排,在具体操作的时候,用ds存放数据段的段地址,在根据需要,用相关指令访问数据段中的具体单元
如,将123B0H ~ 123B9H的内存单元定义为数据段,现在要累加这个数据段中的前3个单元中的数据,代码如下:
mov ax, 123B
mov ds, ax ;将123BH送入ds中,作为数据段的段地址
mov al, 0 ;用al存放累加结果
add al, [0] ;将数据段第一个单元(偏移地址为0)中的数值加到al中
add al, [1] ;将数据段第二个单元(偏移地址为1)中的数值加到al中
add al, [2] ;将数据段第三个单元(偏移地址为2)中的数值加到al中
写几条指令,累加数据段中的前3个字型数据:
mov ax, 123B
mov ds, ax ;ds存放数据段的段地址
mov ax, 0 ;用ax存放累加结果
add ax, [0]
add ax, [2]
add ax, [4]
因为一个字型数据占两个单元,所以偏移地址是0、2、4
栈是一个具有特殊访问方式的存储空间,特殊性在于,最后进入这个空间的数据,最先出去
8086CPU提供了入栈和出栈指令,最基本的两个是PUSH和POP,比如push ax
是将寄存器ax中的数据送入栈中,pop ax
表示从栈顶取出数据送入ax,8086CPU的入栈和出栈操作都是以字为单位进行的。
之前我们讨论过,CPU是如何知道当前要执行的指令所在的位置?是用CS:IP中存放着当前指令的段地址和偏移地址。现在如何知道栈顶的位置?显然也应该有相应的寄存器来存放栈顶的地址,在8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS:SP中得到栈顶的地址。
push ax
的执行由以下两步完成:
可以看出,入栈时,栈顶从高地址向低地址方向增长。
那如果将10000H ~ 1000FH这段空间当作栈,初始状态栈是空的,此时SS = 1000H,SP = 多少?
SP = 0010H,如图:
8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶,但是如果能够保证入栈和出栈时,栈顶不会超出栈空间?
8086CPU不保证我们对栈的操作不会越界,也就是说,只知道栈顶在何处(由SS:SP指示),但是并不知道要执行的指令有多少,因此,我们要自己小心栈顶越界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致越界。
前面我们使用的push ax,pop ax,显然push和pop指令是可以在寄存器和内存之间传送数据的。
push和pop指令的格式可以是如下几种形式:
push 寄存器
pop 寄存器
push 段寄存器
pop 段寄存器
push 内存单元
pop内存单元
比如:
mov ax, 1000H
mov ds, ax
push [0]
pop [2]
编程实现:
mov ax, 1000H
mov ss, ax
mov sp, 0010H ;初始sp在最高地址的下一个单元
mov ax, 001AH
mov bx, 001BH
push ax
push bx
sub ax, ax ;将ax清零,也可以用mov ax,0
sub bx, bx ;sub ax, ax机器码两个字节,mov ax,0机器码3个字节
pop bx
pop ax
编程实现:
mov ax, 1000H
mov ss, ax
mov sp, 0010H ;初始sp在最高地址的下一个单元
mov ax, 001AH
mov bx, 001BH
push ax
push bx
pop ax
pop bx
push ax是入栈指令,将在栈顶之上压入新的数据,执行过程是,先将SP-2,使得SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向新的栈顶单元
注意:push和pop等栈操作指令,修改的只是SP,也就是说栈顶变化范围最大为0 ~ FFFFH
对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N <= 64KB)的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来使用,从而定义了一个栈段。比如我们将10010H ~ 1001FH这段长度为16个字节的内存空间当作栈来用,以栈的方式来进行访问。这段空间就称为一个栈段,段地址为1001H,大小为16字节
一个栈段最大可以设为多少?
栈顶的变化范围是0 ~ FFFFH,从栈空的时候,SP = 0,一直压栈,压满后SP = 0,如果再次压栈,栈顶将环绕,覆盖原先栈中的内容,所以一个栈段容量最大为64KB