汇编语言知识点总结之三:第三章《寄存器(内存访问)》

本文围绕8086CPU展开,介绍了内存中字的存储方式,如字单元由两个连续内存单元组成。阐述了DS和[address]的使用,以及mov、add、sub等指令。还讲解了栈的概念、操作规则,包括入栈和出栈指令,同时强调了栈顶超界问题。最后说明了数据段、栈段的定义及使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、内存中字的存储

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

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

示例如下:在0地址处开始存放20000(4E20H)

注意:0号单元是低地址单元,1号单元是高地址单元。

问题:

1、0地址单元中存放的字节型数据是多少?答:20H

2、0地址字单元中存放的字型数据是多少?答:4E20H

3、2地址单元中存放的字节型数据是多少?答:12H

4、2地址字单元中存放的字型数据是多少?答:0012H

5、1地址字单元中存放的字型数据是多少?答:124EH

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

2、DS和[address]

CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址;在8086PC中,内存地址由段地址和偏移地址组成。8086CPU中有一个 DS寄存器,通常用来存放要访问的数据的段地址。

例如:我们要读取10000H单元的内容可以用如下程序段进行:

mov bx, 1000H

mov ds, bx

mov a1,[0]

上面的三条指令将10000H(1000:0)中的数据读到al中。

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

(1)将数据直接送入寄存器;

(2)将一个寄存器中的内容送入另一个寄存器;

除此之外,mov指令还可以将一个内存单元中的内容送入一个寄存器

从哪一个内存单元送到哪一个寄存器中呢?格式应该为:mov 寄存器名, 内存单元地址。

[…]”表示一个内存单元, “[…]”中的 0 表示内存单元的偏移地址。那么段地址是多少呢?指令执行时,8086 CPU自动取ds(段寄存器)中的数据为内存单元的段地址。

如何用mov指令从10000H中读取数据?

  • 10000H表示为1000:0(段地址:偏移地址)
  • 将段地址1000H放入ds
  • 用mov al,[0]完成传送(mov指令中的[ ]说明操作对象是一个内存单元,[ ]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中)
  • 如何把1000H送入ds?见下:

如何把1000H送入ds?

传送指令 mov ax,1

相似的方式 mov ds,1000H?

8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。(硬件设计的问题)

mov ds,1000H 是非法的。

数据->通用寄存器->段寄存器

问题:写几条指令,将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、字的传送

8086 CPU是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:

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

mov ax, 1000H  //执行后为:ax=1000H

mov ds, ax  //执行后为:ds=1000H

mov ax, [0]  //执行后为:1123H

mov bx, [2]  //执行后为:6622H

mov cx, [1]  //执行后为:2211H

add bx, [1]  //执行后为:8833H

add cx, [2]  //执行后为:8833H

分析:

前两条指令的目的是将ds设为 1000H,

1000:0处存放的字型数据送入ax;

1000:1单元存放字型数据的高8位:11H;1000:0单元存放字型数据的低8位:23H;所以1000:0处存放的字型数据为1123H。

指令执行时,字型数据的高8位送入ah,字型数据的低8位送入al,则ax中的数据为1123H

以下原理同上。

问题 3.4

内存中的情况如图所示,写出下面的指令执行后内存中的值:

分析:进行单步跟踪,看一下每条指令执行后相关寄存器或内存单元中的值。

4、mov、add、sub指令

MOV指令的形式:

mov 寄存器,数据  // mov ax,8

mov 寄存器,寄存器 // mov ax,bx

mov 寄存器,内存单元  // mov ax,[0]

mov 内存单元,寄存器  // mov [0],ax

mov 段寄存器,寄存器 // mov ds,ax

mov 寄存器,段寄存器

add 和 sub 指令同mov 一样,都有两个操作对象。使用形式如下:

5、数据段

对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等)。

我们可以将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。

比如我们用123B0H~123B9H这段空间来存放数据:

  • 段地址:123BH
  • 长度:10字节

将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。

例如:将123B0H - 123BAH 的内存单元定义为数据段。我们现在要累加这个数据段中的前3个单元中的数据,代码如下:

mov ax, 123BH  

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

mov al, [0] // 用al存放累加结果,将数据段第一个单元(偏移地址为0)中的数值加到al中

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

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

问题3.5

写几条指令,累加数据段中的前3个字型数据。

分析:代码如下

mov ax, 123BH

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

mov ax, 0  //用ax寄存器存放累加结果

add ax, [0]  //将数据段第一个字(偏移地址为0)加到ax中

add ax, [2]  //将数据段第二个字(偏移地址为2)加到ax中

add ax, [4]   //将数据段第三个字(偏移地址为4)加到ax中

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

1-5 小结

  • 字在内存中存储时 ,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
  • 用 mov 指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中
  • [address]表示一个偏移地址为 address 的内存单元。
  • 在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
  • mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。

检测点 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

下面的程序执行前,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=0000H
mov al,[0002] AX=00E6H
mov bx,0 BX=0000H
mov bl,[000C] BX=0026H
add al,bl AX=000CH

提示:注意CS的设置

(2)内存中的情况如下所示:

各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0;

(1)写出CPU执行的指令序列(用汇编指令写出)

(2)写出CPU执行每条指令后,CS、IP和相关寄存器中的数值

(3)再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?数据和程序在内存中都是二进制/十六进制存储,根据寄存器进行区别数据、程序

答案:

指令序列

CS

IP

DS

AX

BX

初始值

2000h

0

0

0

0

1

mov ax,6622h

2000h

3h

0

6622h

0

2

jmp 0ff0:0100

ff0h

100h

0

6622h

0

3

mov ax,2000h

ff0h

103h

0

2000h

0

4

mov ds,ax

ff0h

105h

2000h

2000h

0

5

mov ax,[8]

ff0h

108h

2000h

c189h

0

6

mov ax,[2]

ff0h

10bh

2000h

ea66h

0

6、栈

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

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

  • 入栈:将一个新的元素放到栈顶;
  • 出栈:从栈顶取出一个元素。

栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。

栈的操作规则:LIFOLast In First Out,后进先出)

现今的CPU中都有栈的设计,8086 CPU 提供相关的指令来以栈的方式访问内存空间。这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

8086CPU提供入栈和出栈指令: (最基本的)

   PUSH(入栈)

   POP  (出栈)

push ax:将寄存器ax中的数据送入栈中

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

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

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

CPU如何指导当前要执行的指令所在的位置?

答:寄存器CS和IP中存放着当前指令的段地址和   偏移地址。

8086CPU中,有两个寄存器:

  • 段寄存器SS   存放栈顶的段地址
  • 寄存器SP      存放栈顶的偏移地址

任意时刻,SS:SP指向栈顶元素。

PUSH指令的执行过程:由两步完成:

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

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

如下为:8086CPU对push指令的执行过程:

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

问题 3.6

如果我们将 10000H - 1000FH 这段空间当作栈,初始状态是空的,此时,SS=1000H,SP=?

思考后看分析:

SP=0010H

我们将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指令的执行过程: 由两步完成:

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

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

如下为 8086CPU对pop指令的执行过程:

注意:

出栈后,SS:SP指向新的栈顶 1000EH,pop操作前的栈顶元素,1000CH 处的2266H 依然存在 ,但是,它已不在栈中。

当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。

7、栈顶超界的问题

8086CPU用SS和SP指示栈顶的位置,并提供push和pop指令实现入栈和出栈。SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。

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

8086CPU的工作机理,只考虑当前的情况:

  • 当前栈顶在何处;
  • 当前要执行的指令是哪一条。

结论:

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

8、push、pop指令

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

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

push和pop指令的格式一:

  • push 寄存器:将一个寄存器中的数据入栈
  • pop寄存器:出栈,用一个寄存器接收出栈的数据

例如:push ax ; pop bx;

push和pop指令的格式二

  • push 段寄存器:将一个段寄存器中的数据入栈
  • pop段寄存器:出栈,用一个段寄存器接收出栈的数据

例如:push ds; pop es;

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中的数据入栈。

分析:代码如下

mov ax, 1000H
mov ss, ax  //设置栈的段地址,ss=1000H,不能直接向段寄存器ss送入数据,所以用ax中转。
mov sp, 0010H //设置栈顶的偏移地址,因栈为空,所以sp=0010H。
              //上面的三条指令设置栈顶地址,编程时要自己注意栈的大小。
push ax
push bx
push ds

问题 3.8

编程 :(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
push ax
push bx   //ax、bx入栈,
mov ax, 0H
mov bx, 0H
pop bx //从栈中恢复ax、bx原来的数据,当前栈顶的内容是bx
pop ax

图解:

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

问题 3.9

编程:(1)将10000H - 1000FH 这段空间当作栈,初始状态栈是空的:

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

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

分析:代码如下

mov ax, 1000H
mov ss, ax
mov sp, 0010H //初始化栈顶,
mov ax, 002AH
mov bx, 002BH
push ax
push bx   //ax、bx入栈,
pop ax
pop bx

图解:

问题 3.10

我们如果要在 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, 0002H

mov ax, 2266H

push ax

从上述分析中可以看出,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。

栈的综述:

8086CPU提供了栈操作机制,方案如下:

  • 在SS,SP中存放栈顶的段地址和偏移地址;
  • 提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元。

push指令的执行步骤:

  • SP=SP-2;
  • 向SS:SP指向的字单元中送入数据。

pop指令的执行步骤:

  • 从SS:SP指向的字单元中读取数据;
  • SP=SP-2。

任意时刻,SS:SP指向栈顶元素。

8086CPU只记录栈顶,栈空间的大小我们要自己管理。

用栈来暂存以后需要恢复的寄存器的内容时 ,寄存器出栈的顺序要和 入栈的顺序相反。

push、pop实质上是一种内存传送指令,注意它们的灵活应用。

9、栈段

问题 3.11

如果我们将 10000H - 1FFFFH 这段空间当作栈段,初始状态栈是空的,此时,SS=1000H,SP=? 答:SP=0H

分析:任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为 1000:FFFE,所以栈空时,SP=0000H。

问题 3.12

一个栈段最大可以设为多少?为什么?答: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]
add ax, [2]
mov bx, [4]
add bx, [6]
push ax
push bx
pop ax
pop bx

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

一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是,关键在于CPU中寄存器的设置,即:CS、IP、SS、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] 

(2)补全下面的程序,使其可以将 10000H - 1000FH 中的8个字,逆序拷贝到 20000H - 2000FH中。(感觉题目出错了,应该为:将20000H - 2000FH 中的8个字,逆序拷贝到 10000H - 1000FH中)

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 用机器指令和汇编指令编程

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值