零基础入门~汇编语言(第四版王爽)~第3章寄存器(内存访问)

news2024/9/24 17:18:28

文章目录

    • 前言
    • 3.1 内存中字的存储
    • 3.2 DS 和[address]
    • 3.3 字的传送
    • 3.4 mov、add、sub指令
    • 3.5 数据段
    • 检测点3.1
    • 3.6 栈
    • 3.7 CPU提供的栈机制
    • 3.8 栈顶超界的问题
    • 3.9 push、pop指令
    • 3.10 栈 段
    • 检测点3.2
    • 实验2 用机器指令和汇编指令编程

前言

第2章中,我们主要从CPU 如何执行指令的角度讲解了8086CPU的逻辑结构、形成 物理地址的方法、相关的寄存器以及一些指令。读者应在通过了前一章所有的检测点,并 完成了实验任务之后,再开始学习当前的课程。这一章中,我们从访问内存的角度继续学 习几个寄存器。

3.1 内存中字的存储

CPU 中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储时,由于内存单元是 字节单元(一个单元存放一个字节),则一个字要用两个地址连续 的内存单元来存放,这个字的低位字节存放在低地址单元中, 高位字节存放在高地址单元中。比如我们从0地址开始存放 20000,这种情况如图3.1所示。
在这里插入图片描述
在图3. 1 中 , 我们用 0 、1 两个内存单元存放数据 20000(4E20H) 。0 、1 两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。对于这个字单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据 4E20H的低位字节存放在0号单元中,高位字节存放在1号单元中。同理,将2、3号单 元看作一个字单元,它的起始地址为2。在这个字单元中存放数据18(0012H), 则 在 2 号 单元中存放低位字节12H, 在3号单元中存放高位字节00H。
我们提出字单元的概念:字单元,即存放一个字型数据(16位)的内存单元,由两个地 址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中 存放字型数据的低位字节。
在以后的课程中,我们将起始地址为N 的字单元简称为N 地址字单元。比如一个字 单元由2、3两个内存单元组成,则这个字单元的起始地址为2,我们可以说这是2地址 字单元。
##问题3 . 1
对于图3.1:
在这里插入图片描述
(1)0地址单元中存放的字节型数据是多少?
(2)0地址字单元中存放的字型数据是多少?
(3)2地址单元中存放的字节型数据是多少?
(4)2地址字单元中存放的字型数据是多少?
(5)1地址字单元中存放的字型数据是多少?

思考后看分析。 分析:
(1)0地址单元中存放的字节型数据:20H;
(2)0地址字单元中存放的字型数据:4E20H;
(3)2地址单元中存放的字节型数据:12H;
(4)2地址字单元中存放的字型数据:0012H;

(5)1地址字单元,即起始地址为1的字单元,它由1号单元和2号单元组成,用这 两个单元存储一个字型数据,高位放在2号单元中,即:12H, 低位放在1 号单元中,
即:4EH, 它们组成字型数据是124EH, 大小为:4686。
从上面的问题中我们看到,任何两个地址连续的内存单元, N 号单元和 N+1 号 单 元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元 和低位字节单元。

3.2 DS 和[address]

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表示内存单元的偏移地址。我们知道,
只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是多少呢?指令执行 时,8086CPU自动取 ds 中的数据为内存单元的段地址。
再来看一下,如何用 mov 指令从10000H 中读取数据。10000H 用段地址和偏移地址 表示为1000:0,我们先将段地址1000H 放入ds, 然后用mov al,[0]完成传送。mov 指令中 的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地 址默认放在ds 中,指令执行时,8086CPU 会自动从 ds 中取出。
mov bx,1000H
mov ds,bx
若要用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
mov ds,bx
mov [0],al

3.3 字的传送

前面我们用 mov 指令在寄存器和内存之间进行字节型数据的传送。因为8086CPU 是 16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性 传送一个字。只要在 mov 指令中给出16位的寄存器就可以进行16位数据的传送了。 比如:
mov bx,1000H
mov ds,bx
mov ax,[0] (1000:0处的字型数据送入ax)
mov [0],cx (cx 中的16位数据送到1000:0处)

问题3.3
内存中的情况如图3.2所示,写出下面的指令执行后寄存器ax,bx,cx 中的值。
在这里插入图片描述
分析:
进行单步跟踪,看一下每条指令执行后相关寄存器中的值,见表3.1。
在这里插入图片描述
问题3.4
内存中的情况如图3.3所示,写出下面的指令执行后内存中的值,思考后看分析。
在这里插入图片描述
在这里插入图片描述

3.4 mov、add、sub指令

前面我们用到了mov 、add 、sub 指令,它们都带有两个操作对象。 到现在,我们知道,mov 指令可以有以下几种形式。
在这里插入图片描述
我们可以根据这些已知指令进行下面的推测 。
(1)既然有 “mov 段寄存器,寄存器”,从寄存器向段寄存器传送数据,那么也应 该有 “mov 寄存器,段寄存器”,从段寄存器向寄存器传送数据。一个合理的设想是: 8086CPU内部有寄存器到段寄存器的通路,那么也应该有相反的通路。
有了推测,我们还要验证一下。进入 Debug, 用A 命令,如图3.4所示。
在这里插入图片描述
图3.4中,用A 命令在一个预设的地址0B39:0100处,用汇编的形式 mov ax,ds写入 指令,再用T 命令执行,可以看到执行的结果,段寄存器ds 中的值送到了寄存器 ax 中。 通过验证我们知道,“mov 寄存器,段寄存器”是正确的指令。
(2)既然有“mov 内存单元,寄存器”,从寄存器向内存单元传送数据,那么也应 该有“mov 内存单元,段寄存器”,从段寄存器向内存单元传送数据。比如我们可以将段 寄存器cs 中的内容送入内存10000H处,指令如下。
mov ax,1000H
mov ds,ax
mov [0],cs
在 Debug中进行试验,如图3.5所示。
在这里插入图片描述
图3.5中,当CS:IP 指 向 0B39:0105的时候,Debug 显示当前的指令mov [0000],cs, 因为这是一条访问内存的指令,Debug 还显示出指令要访问的内存单元中的内容。由于指 令中的CS 是一个16位寄存器,所以要访问(写入)的内存单元是一个字单元,它的偏移地 址为0,段地址在 ds 中 ,Debug 在屏幕右边显示出“DS:0000=0000”, 我们可以知道这 个字单元中的内容为0。
mov [0000],cs 执 行 后 ,CS 中的数据(0B39H)被写入1000:0处,1000:1 单元存放 OBH,1000:0 单元存放39H。
最后,用 D 命令从1000:0开始查看指令执行后内存中的情况,注意1000:0、1000:1 两个单元的内容。
(3)“mov 段寄存器,内存单元”也应该可行,比如我们可以用10000H 处存放的字 型数据设置ds(即将10000H 处存放的字型数据送入ds), 指令如下。
mov ax,1000H mov ds,ax
mov ds,[0]
可以自行在Debug 中进行试验。
add 和 sub 指令同mov 一样,都有两个操作对象。它们也可以有以下几种形式。
在这里插入图片描述
它们可以对段寄存器进行操作吗?比如“add ds,ax”。请自行在 Debug 中试验。

3.5 数据段

前面讲过(参见2.8节),对于8086PC 机,在编程时,可以根据需要,将一组内存单元 定义为一个段。我们可以将一组长度为 N(N≤64KB) 、 地址连续、起始地址为16的倍数 的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。比如用123BOH~ 123B9H 这段内存空间来存放数据,我们就可以认为,123BOH~123B9H 这段内存是一个 数据段,它的段地址为123BH, 长度为10个字节。
如何访问数据段中的数据呢?将一段内存当作数据段,是我们在编程时的一种安排,可以在具体操作的时候,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据 段中的具体单元。
比如,将123B0H~123B9H 的内存单元定义为数据段。现在要累加这个数据段中的前 3个单元中的数据,代码如下。
在这里插入图片描述
问题3.5
写几条指令,累加数据段中的前3个字型数据,思考后看分析。 分析:
代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

检测点3.1

( 1 ) 在Debug 中,用“d0:01f” 查看内存,结果如下。
在这里插入图片描述
下面的程序执行前,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 的设置)

(2)内存中的情况如图3.6所示。
各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0;
① 写 出CPU 执行的指令序列(用汇编指令写出)。
mov ax,6622
jmp 0ff0:0100
mov ax,2000
mov ds,ax
mov ax,[0008]
mov ax,[0002]
② 写 出CPU 执行每条指令后,CS 、IP 和相关寄存器中的数值。
mov ax,6622(CS:2000 IP:0003)
jmp 0ff0:0100 (CS:0ff0(1000) IP:0100(0000))
mov ax,2000 (CS:1000 IP:0003)
mov ds,ax (CS:1000 IP:0005)
mov ax,[0008](CS:1000 IP:0008)
mov ax,[0002](CS:1000 IP:000B)
③ 再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?
在这里插入图片描述

3.6 栈

这里,我们对栈的研究仅限于这个角度:栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去
可以用一个盒子和3本书来描述栈的这种操作方式。
一个开口的盒子就可以看成一个栈空间,现在有3本书,《高等数学》、《C 语言》、 《软件工程》,把它们放到盒子中,操作的过程如图3 . 7所示。
在这里插入图片描述
现在的问题是, 一次只允许取一本,我们如何将3本书从盒子中取出来?
显然,必须从盒子的最上边取。这样取出的顺序就是:《软件工程》、《C语言》、
《高等数学》,和放入的顺序相反,如图3 .8所示。
在这里插入图片描述
从程序化的角度来讲,应该有一个标记,这个标记一直指示着盒子最上边的书。
如果说,上例中的盒子就是一个栈,我们可以看出,栈有两个基本的操作:入栈和出栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为: LIFO(Last In First Out,后进先出)。

3.7 CPU提供的栈机制

现今的 CPU 中都有栈的设计,8086CPU也不例外。8086CPU 提供相关的指令来以栈的方式访问内存空间。这意味着,在基于8086CPU 编程的时候,可以将一段内存当作栈 来使用。
8086CPU 提供入栈和出栈指令,最基本的两个是PUSH(入栈)和 POP(出栈)。比如,push ax 表示将寄存器 ax中的数据送入栈中,pop ax 表示从栈顶取出数据送入ax。
8086CPU的入栈和出栈操作都是以字为单位进行的。
下面举例说明,我们可以将10000H~1000FH这段内存当作栈来使用。
在这里插入图片描述
mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax
pop bx
pop cx
注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位。
读者看到图3.9所描述的 push 和 pop 指令的执行过程,是否有一些疑惑?总结一下,大概是这两个问题。
其一,我们将10000H~ 1000FH 这段内存当作栈来使用,CPU 执行 push 和 pop 指令 时,将对这段空间按照栈的后进先出的规则进行访问。但是,一个重要的问题是,CPU 如何知道10000H~1000FH这段空间被当作栈来使用?
其二,push ax 等入栈指令执行时,要将寄存器中的内容放入当前栈顶单元的上方, 成为新的栈顶元素;pop ax等指令执行时,要从栈顶单元中取出数据,送入寄存器中。显然 ,push 、pop 在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?
这不禁让我们想起另外一个讨论过的问题,就是,CPU 如何知道当前要执行的指令所在的位置?我们现在知道答案,那就是 CS 、IP 中存放着当前指令的段地址和偏移地址。现在的问题是:CPU 如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU 中,有两个寄存器,段寄存器 SS 和寄存器 SP, 栈顶的段地址存放 在 SS 中,偏移地址存放在SP 中 。任意时刻, SS:SP 指向栈顶元素。push 指令和pop 指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。
现在,我们可以完整地描述 push 和 pop 指令的功能了,例如push ax。 push ax 的执行,由以下两步完成。
(1)SP=SP-2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
( 2 ) 将ax 中的内容送入 SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶。 图3.10描述了8086CPU 对 push 指令的执行过程。
在这里插入图片描述
从图中我们可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

问题3.6
如果将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,SS=1000H, SP=? 思考后看分析。
分析:
SP=0010H, 如图3.11所示。
在这里插入图片描述
将10000H~1000FH 这段空间当作栈段,SS=1000H, 栈空间大小为16字节,栈最底部的字单元地址为1000:000E。任意时刻,SS:SP 指向栈顶,当栈中只有一个元素的时候 ,SS=1000H,SP=000EH 。 栈为空,就相当于栈中唯一的元素出栈,出栈后, SP=SP+2,SP 原来为000EH, 加 2后SP=10H, 所以,当栈为空的时候,SS=1000H,
SP=10H。
换一个角度看,任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素, 也就不存在栈顶元素,所以SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地 址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E, 所以栈空时 ,SP=0010H。
接下来,我们描述pop 指令的功能,例如pop ax。
pop ax的执行过程和push ax 刚好相反,由以下两步完成。
( 1 ) 将SS:SP 指向的内存单元处的数据送入 ax 中;
(2)SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
图3.12描述了8086CPU对pop 指令的执行过程。
在这里插入图片描述
意,图3. 12中,出栈后,SS:SP 指向新的栈顶1000EH,pop 操作前的栈顶元素,1000CH 处的2266H 依然存在,但是,它已不在栈中。当再次执行 push 等入栈指令后,SS:SP 移至1000CH, 并在里面写入新的数据,它将被覆盖。

3.8 栈顶超界的问题

我们现在知道,8086 CPU 用SS和SP指示栈顶的地址,并提供 push 和 pop 指令实现入栈和出栈。
但是,还有一个问题需要讨论,就是 SS 和 SP 只是记录了栈顶的地址,依靠 SS 和 SP 可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?
图3.13描述了在执行 push 指令后,栈顶超出栈空间的情况。
图3. 13中,将10010H~1001FH 当作栈空间,该栈空间容量为16字节(8字),初始状态为空,SS=1000H 、SP=0020H,SS:SP 指向10020H;
在执行8次push ax后,向栈中压入8个字,栈满,SS:SP指向10010H;
再次执行 push ax:sp=sp-2,SS:SP指向1000EH, 栈顶超出了栈空间,ax 中的数据
送入1000EH 单元处,将栈空间外的数据覆盖。
图3.14描述了在执行 pop 指令后,栈顶超出栈空间的情况。
图3. 14中,将10010H~1001FH 当作栈空间,该栈空间容量为16字节(8字),当前状 态为满,SS=1000H 、SP=0010H,SS:SP 指向10010H;
在这里插入图片描述
在这里插入图片描述
在执行8次pop ax后,从栈中弹出8个字,栈空,SS:SP 指向10020H;
再次执行 pop ax:sp=sp+2,SS:SP指向10022H, 栈顶超出了栈空间。此后,如果再
执行push 指令,10020H 、10021H中的数据将被覆盖。
上面描述了执行 push 、pop 指令时,发生的栈顶超界问题。可以看到,当栈满的时候 再使用push 指令入栈,或栈空的时候再使用pop 指令出栈,都将发生栈顶超界问题。
栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很 可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可 能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于 我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。
我们当然希望CPU 可以帮我们解决这个问题,比如说在CPU 中有记录栈顶上限和栈 底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU 在执行 push指令的时候靠检测栈顶上限寄存器、在执行pop 指令的时候靠检测栈底寄存器保证不 会超界。
不过,对于8086CPU, 这只是我们的一个设想(我们当然可以这样设想,如果CPU 是 我们设计的话,这也就不仅仅是一个设想)。实际的情况是,8086CPU 中并没有这样的寄 存器。
8086CPU 不保证我们对栈的操作不会超界。这也就是说,8086CPU 只知道栈顶在何 处(由SS:SP 指示),而不知道我们安排的栈空间有多大。这点就好像CPU 只知道当前要执 行的指令在何处(由 CS:IP 指示),而不知道要执行的指令有多少。从这两点上我们可以看 出8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令 是哪一条。
我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安 排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈 空的时候继续出栈而导致的超界。

3.9 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] ;出栈,出栈的数据送入1000:2处
指令执行时,CPU 要知道内存单元的地址,可以在push 、pop 指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU 从 ds 中取得。

问题3.7
编程,将10000H~1000FH 这段空间当作栈,初始状态栈是空的,将 AX 、BX 、DS 中的数据入栈。
在这里插入图片描述
问题3.8
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态栈是空的;
(2)设置AX=001AH,BX=001BH;
(3) 将AX 、BX 中的数据入栈;
(4)然后将AX 、BX清零;
(5)从栈中恢复AX、BX 原来的内容。
思考后看分析。
分析:
代码如下
在这里插入图片描述
从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序 要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。

问题3.9
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态栈是空的;
(2)设置AX=001AH,BX=001BH;
(3)利用栈 ,交换AX 和 BX 中的数据。
思考后看分析。
在这里插入图片描述
问题3.10
如果要在10000H 处写入字型数据2266H, 可以用以下的代码完成:
mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax
补全下面的代码,使它能够完成同样的功能:在10000H 处写入字型数据2266H。 要求:不能使用 “mov 内存单元,寄存器”这类指令。
在这里插入图片描述

mov ax,2266H
push ax
思考后看分析
我们来看需补全代码的最后两条指令,将ax中的 2266H 压入栈中,也就是说,最终应由push ax将2266H写入10000H处。问题的关键就在于:如何使 push ax访问的内存单 元是10000H。
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~lFFFFH 这段空间当作栈段,初始状态栈是空的,此时, SS=1000H,SP=?
思考后看分析。
分析:
如果将10000H~lFFFFH 这段空间当作栈段,SS=1000H, 栈空间为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。
在这里插入图片描述
在这里插入图片描述

检测点3.2

(1)补全下面的程序,使其可以将10000H~ 1000FH 中的8个字,逆序复制到 20000H~2000FH中。逆序复制的含义如图3.17所示(图中内存里的数据均为假设)。
在这里插入图片描述
答案:
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]

实验2 用机器指令和汇编指令编程

1.预备知识:Debug 的使用
前面实验中,讲了Debug 一些主要命令的用法,这里,再补充一些关于 Debug的知识。
(1)关于D 命令。
从上次实验中,我们知道,D 命令是查看内存单元的命令,可以用“d 段地址:偏移 地址”的格式查看指定的内存单元的内容,上次实验中,D 命令后面的段地址和偏移地址 都是直接给出的。
现在,我们知道段地址是放在段寄存器中的,在 D 命令后面直接给出段地址,是 Debug 提供的一种直观的操作方式。 D 命令是由 Debug 执行的,Debug 在执行 “d 1000:0”这样的命令时,也会先将段地址1000H 送入段寄存器中。
Debug 是靠什么来执行D 命令的?当然是一段程序。 谁来执行这段程序?当然是CPU。
CPU 在访问内存单元的时候从哪里得到内存单元的段地址?从段寄存器中得到。
所 以 ,Debug 在其处理D 命令的程序段中,必须有将段地址送入段寄存器的代码。 段寄存器有4个: CS 、DS 、SS 、ES, 将段地址送入哪个段寄存器呢?
首先不能是CS, 因 为CS:IP 必须指向Debug 处 理D 命令的代码,也不能是SS, 因 为 SS:SP 要指向栈顶。这样只剩下了 DS 和 ES 可以选择,放在哪里呢?我们知道,访问内 存的指令如 “mov ax,[0]” 等一般都默认段地址在ds 中,所以Debug 在执行如 “d 段地址: 偏移地址”这种D 命令时,将段地址送入 ds 中比较方便。
D 命令也提供了一种符合 CPU 机理的格式: “d 段寄存器:偏移地址”,以段寄存器 中的数据为段地址 SA, 列 出 从 SA: 偏移地址开始的内存区间中的数据。以下是几个例子。
在这里插入图片描述
( 2 ) 在E 、A 、U 命令中使用段寄存器。
在 E 、A 、U 这些可以带有内存单元地址的命令中,也可以同 D 命令一样,用段寄存 器表示内存单元的段地址,以下是几个例子。
在这里插入图片描述
(3)下一条指令执行了吗?
在 Debug 中,用A 命令写一段程序:在这里插入图片描述
仔细看一下图3.18中单步执行的结果,你发现了什么问题?
在这里插入图片描述
在用T 命令单步执行 mov ax,2000后,显示出当前 CPU 各个寄存器的状态和下一步 要执行的指令:mov ss,ax;
在用T 命令单步执行mov ss,ax后,显示出当前CPU 各个寄存器的状态和下一步要执 行的指令……,在这里我们发现了一个问题:mov ss,ax的下一条指令应该是mov sp,10, 怎么变成了mov ax,3123?
mov sp,10到哪里去了?它被执行了吗? 我们再仔细观察,发现:
在程序执行前,ax=0000,ss=0b39,sp=ffee
在 用T 命令单步执行 mov ax,2000后 ,ax=2000;ss=0b39;sp=ffee 在用T 命令单步执行mov ss,ax后 ,ax=2000;ss=2000;sp=0010
注意,在用 T 命令单步执行 mov ss,ax 前 ,ss=0b39, sp=ffee, 而执行后 ss=2000,
sp=0010 。ss 变为2000是正常的,这正是mov ss,ax 的执行结果。可是sp 变为0010是怎 么回事?在这期间,能够将sp 设为0010的只有指令 mov sp,10,看 来 ,mov sp,10一 定是 得到了执行。
那么,mov sp,10 是在什么时候被执行的呢?当然是在 mov ss,ax 之后,因为它就是 mov ss,ax的下一条指令。显然,在用T 命令执行mov ss,ax的时候,它的下一条指令mov sp,10也紧接着执行了。
整理一下我们分析的结果:在用T 命令执行 mov ss,ax 的时候,它的下一条指令 mov sp,10 也紧接着执行了。 一般情况下,用T 命令执行一条指令后,会停止继续执行,显示
出当前CPU 各个寄存器的状态和下一步要执行的指令,但T 命令执行mov ss,ax的时候, 没有做到这一点。
不单是 mov ss,ax, 对于如mov ss,bx,mov ss,[0],pop ss 等指令都会发生上面的情 况,这些指令有哪些共性呢?它们都是修改栈段寄存器SS 的指令。
为什么会这样呢?要想彻底说清楚这里面的来龙去脉,在这里还为时过早,因为这涉 及我们在以后的课程中要深入研究的内容:中断机制,它是我们后半部分课程中的一个主 题。现在我们只要知道这一点就可以了:Debug 的 T 命令在执行修改寄存器 SS 的指令 时,下一条指令也紧接着被执行。
2. 实验任务
(1)使用 Debug, 将下面的程序段写入内存,逐条执行,根据指令执行后的实际运行 情况填空。
在这里插入图片描述
(2)仔细观察图3.19中的实验过程,然后分析:为什么 2000:0~2000:f 中的内容会发 生改变?
可能要再做些实验才能发现其中的规律。如果你在这里就正确回答了这个问题,那么 要恭喜你,因为你有很好的悟性。大多数的学习者对这个问题还是比较迷惑的,不过不要紧,因为随着课程的进行,这个问题的答案将逐渐变得显而易见。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2070985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2月公开赛Web-ssrfme

考点&#xff1a; redis未授权访问 源码&#xff1a; <?php highlight_file(__file__); function curl($url){ $ch curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_HEADER, 0);echo curl_exec($ch);curl_close($ch); }if(isset($_GET[url…

回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出…

vue3 生命周期钩子

在 Vue 3 中&#xff0c;可以在组件不同阶段的生命周期执行特定逻辑。 生命周期整体分为四个阶段&#xff1a;创建、挂载、更新、卸载。 创建阶段 组合式APIsetup() 这是组合式 API 的入口点&#xff0c;在组件实例创建之前被调用。在此阶段&#xff0c;可以初始化响应式数据…

一键批量查询邮政快递,物流状态尽在掌握

邮政快递批量查询&#xff0c;轻松掌握物流动态 在电商行业蓬勃发展的今天&#xff0c;邮政快递作为连接商家与消费者的桥梁&#xff0c;其物流信息的及时性和准确性对于提升客户体验至关重要。然而&#xff0c;面对海量的快递单号&#xff0c;如何高效地进行批量查询&#xf…

【最长上升子序列】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1010; int a[N], f[N]; int main() {int n;cin >> n;for(int i 1; i < n; i) cin >> a[i];int res 0;for(int i 1; i < n; i){f[i] 1;for(int j 1; j < i; j){if(a[j] &…

(贪心) LeetCode 135. 分发糖果

原题链接 一. 题目描述 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。 相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果&#xf…

UE5 蓝图 计算当前时间段

思路&#xff1a; 那当前hour与阈值hour对比 。小于返回&#xff0c;大于就继续循环对比。 临时变量 折叠图表↓

快排之自省排序

introsort是introspective sort采⽤了缩写&#xff0c;他的名字其实表达了他的实现思路&#xff0c;他的思路就是进行⾃我侦测和反省&#xff0c;快排递归深度太深&#xff08;sgi stl中使⽤的是深度为2倍排序元素数量的对数值&#xff09;那就说明在这种数据序列下&#xff0c…

数据结构-栈与队列-数组和链表的推广运用-第六天

hello算法 1.数组和队列作为最基础的两种数据结构&#xff0c;区别主要在于&#xff1a; 1.数组是连续存储&#xff0c;因此可以利用一个开始节点的地址直接确定其他的节点地址。 2.链表未绑定的存储顺序&#xff0c;具有更灵活快捷的增删改查。 3.为了解决存储的问题&#xf…

【Simulink】使用简化机械臂系统动力学的抓取和放置任务及轨迹调度

abbSavedConfigs.mat 文件中的配置 文件中保存了多个关节角度配置&#xff0c;每个配置对应不同的机器人操作步骤。这些配置通常用于控制机器人在执行任务时的各个关键姿态和动作。 各个配置的功能解释&#xff1a; configSequence (18x7 double): 功能: 包含了机器人执行任…

外卖系统开发:如何打造一个无缝衔接的用户体验?

在如今高度竞争的外卖市场中&#xff0c;用户体验决定了一个外卖平台的成败。为了打造一个无缝衔接的用户体验&#xff0c;外卖系统的每一个环节都需要精心设计和优化&#xff0c;从用户下单到订单配送&#xff0c;每一步都必须顺畅无阻。本文将探讨如何通过技术手段和代码实现…

使用Python+os+fnmatch搜索文件和目录

一、搜索/home/Download目录下的zip压缩文件 import os from fnmatch import fnmatch# 搜索与pattern匹配的文件和目录 def find_file(pattern, path):result []for root, dirs, files in os.walk(path):for name in files:if fnmatch(name, pattern):result.append(os.path.…

R语言function快速掌握-自定义函数

R语言在生物学中运用的比较多的还是吊包然后使用内置函数进行一次性工作&#xff0c;但是生物信息与计算生物学领域确实低估和忽视了R语言在循环和自定义函数方面的优势。 在R语言中&#xff0c;function 是一个核心概念&#xff0c;它允许用户创建可重用的代码块来执行特定的…

【PHP报错已解决】‘/www/wwwroot/xxxxxx/public/../thinkphp/start.php‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言&#xff1a; 作为开发者&#xff0c;遇到报错信息是在所难免的。然而&#xff0c;有些报错信息可能会让我们感到困惑&…

每日OJ_牛客_淘宝网店(日期模拟)

目录 牛客_淘宝网店&#xff08;日期模拟&#xff09; 解析代码 牛客_淘宝网店&#xff08;日期模拟&#xff09; 淘宝网店__牛客网 解析代码 这是一个变相的日期计算器。只不过2、3、5、7、11月算1天&#xff0c;其他7个月算2天。 既然是一个变相的日期计算器&#xff0c;那…

24暑假算法刷题 | Day39 | 动态规划 VII | LeetCode 198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III

目录 198. 打家劫舍题目描述题解 213. 打家劫舍 II题目描述题解 337. 打家劫舍 III题目描述题解 打家劫舍的一天 &#x1f608; 198. 打家劫舍 点此跳转题目链接 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷…

初出茅庐:怎样获得实习机会——之找到一份工作

如果你刚开始工作&#xff0c;找到工作的最好和最简单的方法之一就是【实习】&#xff1b;许多技术类公司只雇佣实习生或者经验丰富的软件开发者&#xff0c;实习为公司提供了一个独一无二的机会&#xff0c;使其可以在雇用潜在员工之前对他们进行充分的评估。但实习的机会不是…

Pytorch升级之旅——基础概念

目录 一、人工智能简史 三次浪潮 DL,ML,AI三者之间的关系 二、模型评价指标 混淆矩阵 Overall Accuracy ​编辑 Average accuracy Kappa系数 Recall Precision F1 PR曲线 置信度 IOU AP mAP 三、常用包Numpy、pandas、matplotlib Numpy pandas matplotlib…

二叉树【2】遍历

先中后序 先序遍历&#xff1a;根左右 中序遍历&#xff1a;左跟右 后序遍历&#xff1a;左右跟 例图 先序性质 中序性质 后序性质 先序中序确定二叉树 后序中序确定二叉树 先序后序不能确定。 层序&#xff1a;广度优先搜索 如果需要修改树的元素&#xff0c;则需no…

机器学习(第六关--文本特征抽取)

以下内容&#xff0c;皆为原创&#xff0c;制作实属不易&#xff0c;感谢大家的观看和关注。 在此真诚的祝愿大家&#xff0c;生活顺顺利利&#xff0c;身体健健康康&#xff0c;前途似锦。 第一关&#xff1a;机器学习概念和流程http://t.csdnimg.cn/IuHh4第二关&#xff1a;…