第3章 寄存器(内存访问)


title: 第三章 寄存器(内存访问)
date: 2021-08-01 14:48:03
tags: 汇编语言笔记
categories: 汇编语言笔记

在工作室好哥哥们的影响下,也开始喜欢用博客记录自己的学习过程了。
https://afanbird.github.io/
个人博客的地址,由于可能不久之后会换电脑,暂且就不更新在个人博客上了(怕迁移麻烦),用CSDN更新,后面转载到个人博客上面的时候,也会方便很多。笔记是自己在学习中的汇编语言第三版书上的重点知识。之所以记录下来,也是为了自己以后复习知识的时候,能方便的复习。如有不对的地方,欢迎各位师傅指出,若有什么想法,也可以讨论交流,一起学习进步。

第3章 寄存器(内存访问)

3.1 内存中字的存储

CPU中,用16位寄存器来存储一个字。

高8位存放高位字节,低8位存放低位字节。

在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。

比如我们从0地址开始存放20000,这种情况如图3.1所示。

在图3.1 中,我们用0、1两个内存单元存放数据
20000(4E20H)。0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。

对于这个字单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在О号单元中,高位字节存放在1号单元中。

同理,将2、3号单元看作一个字单元,它的起始地址为2。在这个字单元中存放数据18(0012H),则在2号单元中存放低位字节12H,在3号单元中存放高位字节00H。

字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。

在以后的课程中,我们将起始地址为N的字单元简称为N地址字单元。比如一个字单元由2、3两个内存单元组成,则这个字单元的起始地址为2,我们可以说这是⒉地址字单元。

任何两个地址连续的内存单元,N号单元和 N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。

3.1.1 地址单元 和地址字单元的区别

地址单元:就是这个地址单元的字节型数据,(一个字节是8位)

地址字单元:是这个单元,和这个单元的下一位单元一起组成的,存放的是字型数据(一个字是两个字节,是16位)

3.2DS和【address】

CPU 要读写一个内存单元的时候,必须先给出这个内存单元的地址,

在8086PC 中,内存地址由段地址和偏移地址组成。

8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取10000H单元的内容,可以用如下的程序段进行。

mov bx,1000H 将地址1000H存放到bx寄存器中

mov ds , bx 将bx中的内容,存放到ds段寄存器中

mov al,[0] 执行指令时,自动取ds寄存器里面的地址为段地址,把0作为偏移地址

前面我们使用mov指令,可完成两种传送:

①将数据直接送入寄存器;

②将一个寄存器中的内容送入另一个寄存器。

也可以使用mov指令将一个内存单元中的内容送入一个寄存器中。

从哪一个内存单元送到哪一个寄存器中呢?

在指令中必须指明。寄存器用寄存器名来指明,内存单元则需用内存单元的地址来指明。显然,此时mov指令的格式应该是: mov 寄存器名,内存单元地址。

“[…]”表示一个内存单元,“[…]”中的0表示内存单元的偏移地址。我们知道,只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是多少呢?

指令执行时,8086CPU自动取ds 中的数据为内存单元的段地址。

若要用mov al,[0]完成数据从1000:0单元到al的传送,这条指令执行时,ds中的内容应为段地址1000H,所以在这条指令之前应该将1000H送入 ds。

如何把一个数据送入寄存器呢?我们以前用类似“mov ax,1”这样的指令来完成,从理论上讲,我们可以用相似的方式: mov ds,1000H,来将1000H送入 ds。可是,现实并非如此,

8086CPU不支持将数据直接送入段寄存器的操作,ds 是一个段寄存器,所以mov ds,1000H这条指令是非法的。

那么如何将1000H送入 ds呢?只好用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器,如 bx,再将bx中的内容送入ds.

为什么8086CPU不支持将数据直接送入段寄存器的操作?

这属于8086CPU硬件设计的问题,我们只要知道这一点就行了。

问题3.2 写几条指令,将al中的数据送入内存单元10000H中

怎样将数据从寄存器送入内存单元?

从内存单元到寄存器的格式是:“mov 寄存器名,内存单元地址”,

从寄存器到内存单元则是:“mov内存单元地址,寄存器名”。

10000H可表示为1000:0,用ds存放段地址1000H,偏移地址是0,

则mov [0],al可完成从al到10000H的数据传送。

完整的几条指令是:

mov bx,1000H 将段地址1000H送入通用寄存器bx中

mov ds, bx 将bx寄存器的内容送入ds段寄存器中

mov [ 0],al 将al寄存器中的内容送入到10000H的内存单元中

3.3字的传送

前面我们用mov指令在寄存器和内存之间进行字节型数据的传送。

因为8086CPU 是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性传送一个字。

只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。比如:

mov bx,1000H

mov ds, bx

mov ax,[0] ; 1000:0处的字型数据送入ax

mov [ o] , cx ; cx中的16位数据送到1000:0处

内存中的情况如图3.2所示,写出下面的指令执行后寄存器ax,bx,cx 中的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcEPvf3b-1627805968057)(file:///D:/TreedNote/annex/AttAnnex20210713_160130287.png)]

3.4mov、add、sub指令

前面我们用到了mov、add、sub指令,它们都带有两个操作对象。到现在,我们知道,mov指令可以有以下几种形式。

mov 寄存器,数据 比如:mov ax,8

mov 寄存器,寄存器 比如:mov ax,bx

mov 寄存器,内存单元 比如:mov ax,[0]

mov 内存单元,寄存器 比如:mov [0],ax

mov 段寄存器,寄存器 比如:mov ds,ax

(1)既然有 mov 段寄存器 , 寄存器

那么该有mov 寄存器 , 段寄存器

(2)既然有mov 内存单元 , 寄存器

那么该有mov 内存单元 , 段寄存器

(3)mov 段寄存器 , 内存单元也该行

add 和 sub指令同mov一样,都有两个操作对象。它们也可以有以下几种形式。

add寄存器,数据 比如: add ax,8

add寄存器,寄存器 比如: add ax,bx

add寄存器,内存单元 比如: add ax,[0]

add内存单元,寄存器 比如: add [0],ax

sub寄存器,数据 比如: sub ax,9

sub寄存器,寄存器 比如: sub ax,bx

sub寄存器,内存单元 比如: sub ax,[0]

sub内存单元,寄存器 比如: sub [0],ax

3.5数据段

前面讲过(参见2.8节),对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(N≤64KB)、地址连续、起始地址为16 的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。

比如用123BOH123B9H这段内存空间来存放数据,我们就可以认为,123BOH123B9H 这段内存是一个数据段,它的段地址为123BH,长度为10个字节。

如何访问数据段中的数据呢﹖将一段内存当作数据段,是我们在编程时的一种安排,

可以在具体操作的时候,用ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。

比如,将123BOH~123B9H 的内存单元定义为数据段。现在要累加这个数据段中的前3个单元中的数据,代码如下。

mov ax,123BH

mov ds,ax ;将123BH送入ds中,作为数据段的段地址

mov al,0 ;用al存放累加结果

add al,[0] ;将数据段第一个单元(偏移地址为0)中的数值加到al中

add al,[1] ;将数据段第二个单元(偏移地址为1)中的数值加到al中

add al,[2] ;将数据段第三个单元(偏移地址为2)中的数值加到al中

注意:一个字型数据占两个单元,所以偏移地址是0、2、4.

3.1检测点

(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

​ 0 1 2 3 4 5 6 7 8 9 A B C D E F

下面的程序执行前,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

提示,注意ds的设置。

解题:拿到此题时,能算出来第三行指令,用ds段寄存器的段地址0001H ✖ 10 ➕ 0000 = 00010H。(物理地址 = 段地址 + 偏移地址)

当时就不知道怎么做下去了,前面给的两行数字也不懂,后面是看了别人的解析,才恍然大悟。原来此时的内存单元地址就是00010H,但是需要用其他的地址来表示,见(2.8.1内存单元地址小结(重点))。发现00010H可以表现成段地址为0000,偏移地址为0010.也就是0000:0010的形式,可以看出题目给出的第二行就是0000:0010.把62定为0,往下面依次顺推。

此时思路变清晰可见了。注意ax,bx是16位的寄存器,所以高8位放高位字节,低8位放低位字节。而01234,这些字节才1个,所以需要两个连续字节的内容一起。

(2)内存中的情况如图3.6所示解答的问题2的图片,上传错了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSGqM3B4-1627805968060)(file:///D:/TreedNote/annex/AttAnnex20210714_092400780.png)]各寄存器的初始值:CS = 2000H,IP = 0 , DS = 1000H , AX = 0 ,BX = 0.1.写出CPU执行的指令序列(用汇编指令写出。2.写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。3.再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?

解答:

执行过程:

第一步:CS=2000H,IP=0H 地址:20000H

​ 读取第一条指令 mov ax,6622H

​ CS=2000H, IP=03H

​ 执行指令 mov ax,6622H

​ 执行结果:AX=6622H

第二步:地址:20003H

​ 读取第二条指令 jmp 0ff0:0100

​ CS=2000H,IP=03H

​ 执行指令:jmp 0ff0:0100

​ 执行结果:CS=0ff0H, IP=0100H

第三步:地址:CSx16+IP = 10000H

​ 读取指令:mov ax,2000H

​ CS=0ff0H, IP=0103H

​ 执行指令:mov ax,2000H

​ 执行结果:AX=2000H

第四步:地址:10003H

​ 读取指令:mov ds,ax

​ CS=0ff0H, IP=0105H

​ 执行指令:mov ds,ax

​ 执行结果:ds=2000H

第五步:地址:10005H

​ 读取指令:mov ax,[0008]

CS=0ff0H,IP=10008H,DS=2000H

​ 执行指令:mov ax,[0008] ;

也就是将地址【DS:IP】为 2000H:0008H的值移动到AX

​ 执行结果:AX=C189H

第六步:读取指令:mov ax,[0002]

​ CS=0ff0H ,IP=1000BH,DS=2000H

​ 执行指令:mov ax,[0002]

​ 执行结果:AX=EA66

这里学习了一下别人的思想,如下。

————————————————
版权声明:本文为CSDN博主「Cfreezhan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/freezhanacmore/article/details/17740999

3.由CS:IP组成的物理地址所指向的内存单元中的内容为指令。
由DS:偏移地址组成的物理地址所指向的内存单元中的内容为数据

3.1.1检测点得出的结论(数据和指令的区别)

由CS:IP组成的物理地址所指向的内存单元中的内容为指令。

由DS:偏移地址组成的物理地址所指向的内存单元中的内容为数据

3.6栈

在这里,我们对栈的研究仅限于这个角度:

栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。(后进先出)

如果说,上例中的盒子就是一个栈,我们可以看出,

栈有两个基本的操作:入栈和出栈。

入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。

栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last InFirst Out,后进先出)。

3.7CPU提供的栈机制和push指令的功能

现今的CPU中都有栈的设计,8086CPU也不例外。

8086CPU提供相关的指令来以栈的方式访问内存空间。

这意味着,在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

8086CPU 提供入栈和出栈指令,最基本的两个是 PUSH(入栈)和 POP(出栈)。

比如,push ax表示将寄存器ax 中的数据送入栈中,

pop ax表示从栈顶取出数据送入ax。

8086CPU 的入栈和出栈操作都是以字为单位进行的。

注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位。

问题一:CPU如何知道10000H-1000FH这段内存当作栈来使用?

问题二:显然,push,pop在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道?

这跟之前的问题有点像:CPU如何知道当前要执行的指令所在的位置?

答:那就是CS、IP中存放着当前指令的段地址和偏移地址。

现在的问题是:CPU 如何知道栈顶的位置?

显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU 中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS 中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push 指令和pop指令执行时,CPU 从 SS和SP中得到栈顶的地址。

push ax的执行,有以下两步完成。

(1) SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;

(2)将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

从图中我们可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

问题3.6栈空的状态(重点)

栈空,SS:SP指向栈空间最高地址单元的下一个单元(重点)

执行push ax后,SS:SP指向栈中的第一个元素。

栈空状态的解释:

1.将10000H~1000FH这段空间当作栈段,SS=1000H,栈空间大小为16字节,栈最底部的字单元地址为1000:000E。任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=000EH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2,(出栈SP = SP + 2 , 入栈 SP = SP -2)SP原来为000EH,加2后SP=10H,所以,当栈为空的时候,SS=1000H,SP=10H。

2.换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H。

3.7.1pop指令的功能(入栈和出栈)

pop ax 的执行过程和push ax 刚好相反,由以下两步完成。

(1)将SS:SP指向的内存单元处的数据送入ax中;

(2) SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

注意,图3.12中(电子书73页,书61页),

出栈后,SS:SP指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是,它已不在栈中。当再次执行 push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。

入栈时,先SP-2,指向前一个单元为新栈顶,再将ax的内容送入SS:SP指向的内存单元。

出栈时,先将SS:SP的数据放入ax中,再SP+2,SS:SP指向当前栈顶的下一个单元为新栈顶。

3.8栈顶超界的问题

我们现在知道,8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。

但是,还有一个问题需要讨论,

就是SS 和 SP只是记录了栈顶的地址,依靠SS和S可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?

上面描述了执行push、pop 指令时,发生的栈顶超界问题。可以看到,

当栈满的时候再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。

栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。

我们当然希望CPU可以帮我们解决这个问题,比如说在CPU中有记录栈顶上限和栈底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU 在执行push 指令的时候靠检测栈顶上限寄存器、在执行pop指令的时候靠检测栈底寄存器保证不会超界。

但实际情况就是8086CPU没有这样的寄存器。

8086CPU不保证我们对栈的操作不会超界。这也就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道我们安排的栈空间有多大。这点就好像CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道要执行的指令有多少。从这两点上我们可以看出8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令是哪一条。

我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

3.9push、pop指令

前面我们一直在使用push ax和 pop ax,

显然push和pop指令是可以在寄存器和内存(栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。)之间传送数据的。

push和pop指令的格式可以是如下形式:

push 寄存器 ;将一个寄存器中的数据入栈

pop 寄存器 ;出栈,用一个寄存器接收出栈的数据

当然也可以是如下形式:

push 段寄存器 ;将一个段寄存器中的数据入栈

pop 段寄存器 ;出栈,用一个段寄存器接收出栈的数据

push和 pop也可以在内存单元和内存单元之间传送数据,我们可以:

push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)

pop 内存单元 ;出栈,用一个内存字单元接收出栈的数据

指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds中取得。

问题3.7将10000H~1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈。

编程,将10000H~1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈。

mov ax , 1000H

mov ss , ax ;设置栈的段地址,SS = 1000H, 不能直接向段寄存器SS中送入数据,所以用ax中转。

mov sp , 0010H ;设置栈顶的偏移地址,因栈为空,所sp=0010H。 如果对栈空时SP的设置有问题,复习问题3.6。

上面三条指令设置栈顶地址。编程时要自己注意栈的大小。

push ax

push bx

push ds

入栈是SP先减2,再将ax bx ds的数据传入到SS:SP指向的内存单元中。

问题3.8从栈中恢复AX、BX原来的内容。

编程:

(1)将10000H~1000FH 这段空间当作栈,初始状态栈是空的;

(2)设置AX=001AH,BX=001BH;

(3)将AX、BX中的数据入栈;

(4)然后将AX、BX清零:

(5)从栈中恢复AX、BX原来的内容。

分析:

mov ax ,1000H

mov ss , ax

mov sp , 0010H ;初始化栈顶

mov ax , 001AH

mov bx , 001BH ;设置ax , bx 的值

push ax

push bx ;ax , bx 的数据入栈

mov ax , 0

mov bx , ax ;将ax bx清零

pop bx

pop ax ;从栈中恢复ax 、 bx原来的数据

从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。(后进先出)

问题3.9利用栈,交换AX和BX中的数据。

编程:

(1)将10000H~1000FH 这段空间当作栈,初始状态栈是空的;

(2)设置AX=001AH,BX=001BH;

(3)利用栈,交换AX和BX中的数据。

分析:

mov ax , 1000H

mov ss , ax

mov sp , 0010H ;初始化栈顶

mov ax ,001AH

mov bc , 001BH ;设置ax , bx的值

push ax

push bx ;ax , bx的数据入栈

pop ax

pop bx ;bx的数据是后进的,所以得先出,所以用ax寄存器去存放它,就达到了交换数据的目的

问题3.10在10000H处写入字型数据2266H.

如果要在10000H处写入字型数据2266H,可以用以下的代码完成:

mov ax ,1000H

mov ds , ax

mov ax , 2266H

mov 【0】 , ax

补全下面的代码,使它能够完成同样的功能:在10000H处写入字型数据2266H.

要求:不能使用“mov内存单元,寄存器”这类指令。

mov ax , 1000H

mov ss ,ax

mov sp , 2

mov ax ,2266H

push ax

我们来看需补全代码的最后两条指令,将ax 中的 2266H压入栈中,也就是说,最终应由push ax 将2266H写入10000H处。问题的关键就在于:如何使push ax访问的内存单元是10000H。

这个问题,我一开始想到是,

mov ax , 1000H

mov ss , ax

mov sp , 0

但是push ax 是入栈指令,它将在栈顶之上压入新的数据。一定要注意:它的执行过程是,先将记录栈顶偏移地址的SP寄存器中的内容减2,使得SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向的新的栈顶单元。

所以,要在执行push ax 之前,将SS:SP指向10002H(可以设SS=1000H,SP=0002H),这样,在执行 push ax 的时候,CPU 先将SP=SP-2,使得 SS:SP指向10000H,再将ax 中的数据送入 SS:SP指向的内存单元处,即10000H处。

完成的程序如下:

mov ax , 1000H

mov ss,ax

mov sp , 2

mov ax , 2266H

push ax

从问题3.10的分析中可以看出,push、pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push 和 pop 指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,push和pop指令还要改变SP中的内容。

我们要十分清楚的是,push和 pop 指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送,而执行 push、pop指令却需要两步操作。

执行 push时,CPU 的两步操作是:先改变SP,后向SS:SP处传送。执行 pop时,CPU的两步操作是:先读取ss:SP处的数据,后改变SP。

注意,push,pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH。

提供:SS、SP指示栈顶;

改变SP后写内存的入栈指令;

读内存后改变SP的出栈指令。这就是8086CPU提供的栈操作机制。

3.10栈段

前面讲过(参见2.8节),对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N≤64KB)的一组地址连续、起始地址为 16的倍数的内存单元,当作栈空间来用,从而定义了一个栈段。

比如,我们将10010H~1001FH 这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一个栈段,段地址为1001H,大小为16字节。

将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就在执行push、pop等栈操作指令时自动地将我们定义的栈段当作栈空间来访问。如何使得如 push、pop等栈操作指令访问我们定义的栈段呢?前面我们已经讨论过,就是要将SS:SP指向我们定义的栈段。

3.11如果将10000H~1FFFFH 这段空间当作栈段,初始状态栈是空的,此时,ss=1000H,SP=?

如果将10000H~1FFFFH 这段空间当作栈段,初始状态栈是空的,此时,ss=1000H,SP=?

如果将10000H~1FFFFH 这段空间当作栈段,SS=1000H,栈空间为64KB,(FFFF就是65536,2的16次方,也就是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。

问题3.12一个栈段最大可以设为多少?为什么?

这个问题显而易见,提出来只是为了提示我们将相关的知识融会起来。

首先从栈操作指令所完成的功能的角度上来看,push、pop等指令在执行的时候只修改SP,

所以栈顶的变化范围是0~FFFFH,从栈空时候的SP=0,一直压栈,直到栈满时SP=0;

如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。所以一个栈段的容量最大为64KB。

段的综述

我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。

我们可以用一个段存放数据,将它定义为“数据段”;

我们可以用一个段存放代码,将它定义为“代码段”:

我们可以用一个段当作栈,将它定义为“栈段”。

我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:

对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU 就将我们定义的数据段中的内容当作数据来访问;

对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;

对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样 CPU在需要进行栈操作的时候,比如执行 push、pop指令等,就将我们定义的栈段当作栈空间来用。

可见,不管我们如何安排,

CPU将内存中的某段内容当作代码,是因CS:IP指向了那里;

CPU将某段内存当作栈,是因为SS:SP 指向了那里。

我们一定要清楚,什么是我们的安排,以及如何让 CPU 按我们的安排行事。要非常清楚 CPU 的工作机理,才能在控制 CPU按照我们的安排运行的时候做到游刃有余。

比如我们将10000H~1001FH安排为代码段,并在里面存储如下代码:

mov ax, 1000H

mov ss, ax

mov sp, 0020H ;初始化栈顶

mov ax,cs

mov ds,ax ;设置数据段段地址

mov ax,[0] ;将10000H处的两个内存单元的内容送入ax,由上到下是从低到高。

add ax,[2] ;将10002H处的内存单元内容加到ax上

mov bx,[4] ;将10004H处的内存单元内容加到bx上

add bx,[6] ;将10006H处的内存单元内容加到bx上

;此处不懂,见3.3字的传送

push ax

push bx

pop ax

pop bx ;这四条指令,交换了ax,bx的数据

设置CS=1000H,IP-0,这段代码将得到执行。可以看到,在这段代码中,我们又将10000H~1001FH 安排为栈段和数据段。10000H~1001FH这段内存,既是代码段,又是栈段和数据段。

一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也是。关键在于CPU中寄存器的设置,即CS、IP. SS,SP,DS的指向。

检测点3.2

(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]

push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)

指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds中取得。

(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]

此处借用了别人的思路。如下

————————————————
版权声明:本文为CSDN博主「就是217」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42777804/article/details/90512159

0FH中的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]

push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)

指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds中取得。

(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]

此处借用了别人的思路。如下

————————————————
版权声明:本文为CSDN博主「就是217」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42777804/article/details/90512159

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值