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