第三章 寄存器(内存访问)
3.1内存中字的存储
- cpu中,用16位寄存器来存储一个字。高8位字节,低8位字节。在内存中存储时,由于内存单元字节(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在地址单元中,高位字节存放在高地址单元中。比如我们从0地址开始存放20000,情况如图:
在图中,我们用0、1两个内存单元存放数据20000(4E20H)。0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,有0、1两个字节单元组成)。0是低位字节,1是高位字节 - 所以现在提出字单元概念:字单元,即存放在一个字型数据的内存单元,由两个地址连续的内存单元组成。高地址内存单元中
3.2 DS和[address]
- cpu要读些一个内存单元的时候,必须先给出这个内存单元的地址,在8086cpu中,内存地址由福安地址和偏移地址组成。8086cpu中有一个DS寄存器,通常用来存放要访问数据的段地址。
- 我们要读取1000H单元的内容,可以用下面程序进行
mov bx,1000H
mov ds,bx
mov al,[0]
上面3条指令将1000H(1000:0)中的数据读到al中
- mov:1.将数据直接送入寄存器;2.讲一个寄存器中的内容送入另一个寄存器。
- 也可以mov指令将一个内存单元种的内容送入一个寄存器中。此时mov指令格式应该是:mov寄存器名,内存单元地址
- “[…]”表示一个内存单元,“[…]”中的0表示内存单元的偏移地址
- 只有偏移地址 是不能定位一个内存单元的,内存单元的段地址是多少?执行指令时,8086cpu自动取ds中的数据为内存单元的段地址
- 10000H用段地址和偏移地址表示为1000:0,我们现将段地址10000H放入ds,然后用mov al,[0]完成传送。mov指令中的[]说明操作对象时一个内存单元,[]中的0说明这个内存单元的偏移地址是0.段地址默认放在ds中。指令执行时,8086cpu会自动从ds取出
3.3 字的传送
mov ax,1000H
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1]
add bx,[1]
add cx,[2]
内存情况示意
- 执行指令后相关寄存器的值
mov ax,1000H
mov ds,ax
mov ax,11316
mov[0],ax
mov bx,[0]
sub bx,[2]
mov [2],bx
- 内存情况示意
- 执行指令与寄存器中的内容
3.4 mov,add,sub指令
mov 寄存器,数据 比如:mov ax,8
mov 寄存器,寄存器 比如: mov ax,bx
mov 寄存器,内存单元 比如:mov ax,[0]
mov 内存单元,寄存器 比如:mov [0],ax
mov 段寄存器,寄存器 比如:mov ds,ax
-
既然有“mov 段寄存器,寄存器”,从寄存器向段寄存器传送数据,那么也应该“mov 寄存器,段寄存器”,从段寄存器向寄存器传送数据。一个设想是:8086cpu内部有寄存器到段寄存器的通路,那么也有相反通路
-
有了推测,我们还要验证一下。
-
用A命令在一个预设的地址0B39:0100处,用汇编的形式mov ax,ds 写入指令,再用T命令执行,可以看到执行的结果,段寄存器ds 中的值送到了寄存器ax 中。通过验证我们知道,“mov寄存器,段寄存器”是正确的指令。
-
既然有“mov内存单元,寄存器”,从寄存器向内存单元传送数据,那么也应该有“mov内存单元,段寄存器”,从段寄存器向内存单元传送数据。比如我们可以将段寄存器cs 中的内容送入内存10000H 处,指令如下。
mov ax,1000H
mov ds,ax
mov [0],cs
在Debug中进行试验
-
当CS:IP指向0B39:0105的时候,Debug 显示当前的指令mov [0000],cs,因为这是一条访问内存的指令,Debug 还显示出指令要访问的内存单元中的内容。由于指令中的CS是一个16位寄存器,所以要访问(写入)的内存单元是一个字单元,它的偏移地址为0,段地址在 ds 中,Debug 在屏幕右边显示出“DS:0000=0000”,我们可以知道这个字单元中的内容为0。
-
add和sub指令同mov一样,都有两个操作对象,有以下形式
add 寄存器,数据 如:add ax,8
add 寄存器,寄存器 如:add ,ax,bx
add 寄存器,内存单元 如:add ax,[0]
add 内存单元,寄存器 如:add [0],ax
sub 寄存器,数据 如:sub ax,8
sub 寄存器,寄存器 如:sub ,ax,bx
sub 寄存器,内存单元 如:sub ax,[0]
sub 内存单元,寄存器 如:sub [0],ax
3.5 数据段
- 比如,将123B0H~123B9H的内存单元定义为数据段。现在要累加这个数据段中的言3个单元中的数据,代码如下。
mov ax,123BH
mov ds,ax; 将123BH送入ds 中,作为数据段的段地址
mov al,0; 用al存放累加结果
add al,[0]; 将数据段第一个单元(偏移地址为o)中的数值加到al中
add al, [1]; 将数据段第二个单元(偏移地址为1)中的数值加到al中
add al,[2]; 将数据段第三个单元(偏移地址为2)中的数值加到al中
3.6 栈
- 栈是一种具有特殊的访问方式的存储空间。它的特殊性在于,最后进入这个空间的数据,最先出去
- 用一个盒子3本书演示:
入栈的方式
- 现在问题是,一次只允许取一本书,该怎么将3本书从盒子取出来
- 必须从盒子最上面开始取。
出栈的方式
3.7cpu提供的栈机制
- 8086cpu提供入栈和出栈指令,最基本的两个是push(入栈)和pop(出栈)。8086cpu的入栈和出栈操作都是以字为单位进行
8086cpu的栈操作
mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop bx
pop cx
- 字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位
其一,我们将10000H-100FH这段内存当作栈来使用,cpu执行push和pop指令时,将对这段空间按照栈的后进先出的规则进行访问。但是,一个重要的问题是,cpu如何知道10000H-100FH这段空间被当作栈来使用?
其二,push ax等入栈指令 执行时,要将寄存器中的内容放入当前栈顶单元的上方,成为新的的栈顶元素;pop ax 等指令执行时,亚聪栈顶单元取出数据,送入寄存器中。显然,push pop 在执行的时候,必须知道哪个单元是栈顶单元
- 现在,我们可以完整的描述push和pop指令的功能,例如push ax
push ax的执行,由以下两步完成
- SP=SP-2。SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
- 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶
3.8 栈顶超界的问题
- 我们现在知道,8086cpu用SS和SP只是记录了栈顶的地址,依靠SS和SP只是记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。如何能够保证在入栈、出栈时,栈顶不会超出栈空间
执行push后栈顶超出栈空间
执行pop后栈顶超出栈空间
在执行8次pop ax后,从栈中弹出8个字,栈空,SS:SP指向10020H;
再次执行pop ax: sp=sp+2,SS:SP指向10022H,栈顶超出了栈空间。此后,如果再执行push指令,10020H、10021H中的数据将被覆盖。
3.9 push、pop指令
前面我们一直使用push ax和pop ax,显然push 和pop指令是可以在寄存器和内存(栈空间淡然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行)之间传送数据的
push和pop指令的格式可以是如下
push 寄存区;
pop 寄存器;
当然也可以是如下形式:
push 段寄存器;将一个段寄存器中的数据入栈
pop 段寄存器;出栈,用一个段寄存器
push 和 pop也可以在内存单元和内存单元之间传送数据,我们可以
push内存单元;将一个内存字单元处字入栈(注意:找操作都是以字为单元)
pop内存单元;出栈,用一个内存字单元接受出栈的数据
必须:
mov ax,1000H
mov ds,ax;内存单元的段地址要放在ds中
push [0];将1000:0处的字压入栈中
pop[2];出栈,用一个,内存字单元接受出栈的数据
比如:
mov ax,1000H
mov ds,ax;内存单元的段地址要放在ds中
push [0]; 将1000:0处的字压入栈中
pop [2];出栈,出栈的数据送入1000:2处
指令执行时,cpu要知道内存单元的地址,可以在push、pop指令中只给出内存单元的便宜地址,段地址在指令执行时,cpu从ds中取得
问题:1.将10000H-1000FH这段空间当作栈,初始状态栈是空的;
2.设置AX=001AH,BX=001BH
3.利用栈,交换AX和BX中的数据
mov ax,1000H
mov ss,ax
mov sp,0010H;初始化栈顶,栈的情况如图1所示
mov ax,001AH
mov bx,001BH
push ax
push bx; ax、bx入栈,栈的情况如图2
pop ax; 当前栈顶的数据是bx中原来的数据:001BH;
;所以先pop ax,ax=001BH;
pop bx ; 执行pop ax后,栈顶的数据为ax原来的数据;所以再pop bx,bx=001AH
图1
图二
3.10 栈段
- 将一段内存当作栈段,仅仅是我们再编程时的一种安排,cpu并不会由于这种暗炮,就在执行push、pop等站操作指令时自动地将我们定义的栈段当作栈空间来访问。如何使得如push、pop等栈操作指令访问我们定义的栈段呢?就是要讲SS:SP指向我们定义的栈段。
如果讲10000H-1FFFFH这段空间当作栈段,初始状态栈是空的,此时,SS=1000H,SP=?
如果将10000H~1FFFFH这段空间当作栈段,SS=1000H,栈空间为64KB,栈最底部的字单元地址为1000:FFFE。任意时刻,SS:SP指向栈顶单元,当栈中只有一个元素的时候,SS=1000H,SP=FFFEH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2。
SP原来为FFFEH,加2后SP=o,所以,当栈为空的时候,SS=1000H,SP=O。换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以 SS:SP只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H