汇编语言(第3版)学习

汇编语言(第3版)

第一章

1.1 机器语言

机器语言是机器指令的集合。机器指令展开来讲就是一台机器可以正确执行的命令。电子计算机的机器指令是一列二进制数字。计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算。

CPU

CPU是一种微处理器,来执行机器指令、进行运算。每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以每-种微处理器都有自己的机器指令集,也就是机器语言。

应用8086CPU完成运算s=768+12288-1280,机器码如下:

101110000000000000000011
000001010000000000110000
001011010000000000000101

1.2 汇编语言的产生

由于机器语言难于辨认和记忆,给整个产业发展带来了障碍。于是汇编语言产生了。

此后,程序员们就用汇编指令编写源程序。可是,计算机能读懂的只有机器指令,那么如何让计算机执行程序员用汇编指令编写的程序呢?这时,就需要有一个能够将汇编指令转换成机器指令的翻译程序,这样的程序我们称其为编译器。程序员用汇编语言写出源程序,再用汇编编译器将其编译为机器码,由计算机最终执行。图1.1描述了这个工作过程。

在这里插入图片描述

例如:机器指令1000100111011000 表示把寄存器BX的内容送到AX中。汇编指令则写成mov ax,bx。这样的写法与人类语言接近,便于阅读和记忆。

操作:寄存器BX的内容送到AX中

机器指令: 1000100111011000

汇编指令: mov ax,bx

寄存器,是CPU中一个可以存储数据的器件,一个CPU中有多个寄存器。AX,BX分别是两个寄存器的代号。

1.3 汇编语言的产生

汇编语言发展至今,有以下3类指令组成:

  1. 汇编指令:机器码的助记符,有对应的机器码
  2. 伪指令:没有对应的机器码,由编译器执行,计算机并不执行
  3. 其他符号:如+、-、*、/等,由编译器识别,没有对应的机器码

汇编语言的核心是汇编指令,它决定了汇编语言的特性。

1.4 存储器

CPU是计算机的核心部件,它控制整个计算器的运作并进行运算。

要想让一个CPU工作,就必须向它提供指令和数据。

指令和数据在存储器中存放,也就是我们平时所说的内存。

在一台PC机中,内存的作用仅次于CPU。离开了内存,性能再好的CPU也无法工作。这就像再聪明的大脑,没有了记忆也无法进行思考。

磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,就无法被CPU使用。

要灵活地利用汇编语言编程,我们首先要了解CPU是如何从内存读取信息,以及向内存中写入信息的。

1.5 指令和数据

指令和数据是应用上的概念。

在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。

CPU在工作的时候把有的信息看作指令,把有的信息看作数据。为同样的信息赋予了不同的意义。

例如,内存中的二进制信息1000100111011000,计算机可以把它看作大小为89D8H的数据来处理,也可以看作指令mov,ax,bx来执行

1000100111011000 —>89D8H(数据)

1000100111011000 —>mov,ax,bx(程序)

1.6 存储单元

存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号。

例如一个存储器有128个存储单元,编号从0~127,如图1.2所示:

在这里插入图片描述
那么一个存储单元能存储多少信息呢?

我们知道电子计算机的最小信息单位是bit,也就是一个二进制位。

8bit=1Byte(字节)。

微型机存储器的存储单元可以存储一个Byte,即8个二进制位。

一个存储器有128个存储单元,他可以存储128Byte。

微型存储器的容量以字节为最小值单位来计算。对于拥有128个存储单元的存储器,我们可以说,它的容量是128个字节。

1.7 CPU对存储器的读写

以上讲到,存储器被划分成多个存储单元,存储单元从0开始顺序编号。

CPU要从内存中读数据,首先要指定存储单元的地址,也就是说它要先确定读取哪一个存储单元中的数据。

另外,在一台微机中,不只有存储器这一种器件。CPU在读写数据时还要指明

他要对哪一个期间进行操作,进行什么操作,是读数据还是写数据。

可见,CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行3类信息的交互:

  • 存储单元的地址**(地址信息)**
  • 器件的选择,读或写命令**(控制信息)**
  • 读或写的数据**(数据信息)**

那么CPU通过什么将这三类信息传到存储器芯片中呢?

电子信息传输处理的都是电信号,电信号用导线传送。

导线:在计算机中专门连接CPU和其他芯片的导线

根据传送信息的不同,总线分为三类

  • 地址总线
  • 控制总线
  • 数据总线

CPU从3号单元中读取数据的过程,如图1.3所示:
在这里插入图片描述

过程如下:

(1)CPU通过地址线将地址信息3发出。
(2) CPU通过控制线发出内存写命令,选中存储器芯片,并通知它,要向其中写入数据。
(3) CPU 通过数据线将数据26送入内存的3号单元中。

1.8 地址总线:

CPU是通过地址总线来指定存储器单元的。

可见地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。

现假设,一个CPU有10根地址总线,让我们来看一下它的寻址情况。我们知道,在电子计算机中,一根导线可以传送的稳定状态只有两种,高电平或是低电平。用二进制表示就是1或0,10 根导线可以传送10位二进制数据。而10位二进制数可以表示多少个不同的数据呢? 2的10次方个。最小数为0,最大数为1023。

图1.4 展示了一个 具有10根地址线的 CPU向内存发出地址信息 11时10根地址线上传送的二进制信息。
在这里插入图片描述
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。

1.9 数据总线

CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。

数据总线的宽度决定了CPU和外界的数据传送速度。

8根数据总线一次可传送一个8位二进制数据(即一个字节)。16根数据总线一次可传送两个字节。

8088CPU的数据总线宽度为8,8086CPU的数据总线宽度为16。
图1.5展示了8088CPU数据总线上的数据传送情况; 图1.6展示了8086CPU 数据总线上的数据传送情况。

8088CPU分两次传送89D8,第一次传送D8,第二次传送89。

在这里插入图片描述

8086CPU一次传送89D8。

在这里插入图片描述

8086有16根数据线,可一次传送16 位数据,所以可一次传送数据89D8H;而8088只有8根数据线,一次只能传8位数据,所以向内存写入数据89D8H时需要进行两次数据传送。

1.10 控制总线

CPU对外部器件的控制是通过控制总线来进行的。

控制总线是一些不同控制线的集合。有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。所以,控制总线的宽度决定了CPU对外部器件的控制能力。

检测1.1

(1) 1个CPU的寻址能力为8KB, 那么它的地址总线的宽度为13
解题:2^ N=81024=(2^13)=8192*

2^N=8192,即N=13

(2) 1KB 的存储器有1024个存 储单元。存储单元的编号从01023
(3)1KB的存储器可以存储 8192bit,1024Byte。

解题:8个bit组成一个Byte,1KB= 1024B。
(4) 1GB、 1MB、 1KB分别是1073741824、1048576、1024
_Byte。

解题:因为1KB= 1024B
1MB= 1024KB
1GB= 1024MB
1TB= 1024GB

(5) 8080、 8088、 80286、 80386的地址总线宽度分别为16根、20根、24根、32根,则它们的寻址能力分别为:64 (KB)、1 (MB)、 16(MB)、4 (GB)。

解题:一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。

2^16=x*1024,即x=64

以此类推

(6) 8080、 8088、8086、 80286、 80386 的数据总线宽度分别为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:_1(B)、1 (B)、 2(B)、2(B)、4 (B)。

解题:8根数据总线一次可传送一个8位二进制数据(即一个字节),16根数据总线可传递两个字节,32根数据总线可传递四个字节。

(7)从内存中读取 1024字节的数据,8086 至少要读512次, 80386 至少要读256次。

解题:1字节=8位,1024字节=8192位,8086是16位计算机一次读取16位 ,1024B/2B=512,所以512次16位。

80386是32位计算机一次读取32位,1024B/4B=256,所以256次32位。

(8)在存储器中,数据和程序以二进制形式存放。

1.11 内存地址空间(概述)

什么是内存地址空间呢?

举例来讲,一个CPU的地址总线宽度为10, 那么可以寻址1024个内存单元,这1024 个可寻到的内存单元就构成这个CPU的内存地址空间。

1.12 主板

在每一台PC机中,都有一个主板,主板上有核心器件和一些主要器件,这些器件通过总线(地址总线、数据总线、控制总线)相连。

这些器件有CPU、存储器、外围芯片组、扩展插槽等。

扩展插槽上一般插有RAM内存条和各类接口卡。

1.13 接口卡

CPU对外部设备都不能直接控制,如显示器、音箱、打印机等。直接控制这些设备进行工作的是插在扩展插槽上的接口卡。

扩展插槽通过总线和CPU相连,所以接口卡也通过总线同CPU相连。

简单来讲,是CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设进行工作。

1.14 各类存储器芯片

一台PC机中,装有多个存储器芯片,这些存储器芯片从物理连接上看是独立的、不同的器件。

读写属性上看分为两类:

1.随机存储器(RAM):可读可写,但必须带电存储,关机后存储的内容丢失;

2.只读存储器(ROM):只能读取不能写入,关机后其中的内容不丢失。

这些存储器从功能和连接上又可分为以下几类。

  • 随机存储器
  • 装有BIOS的ROM
  • 接口卡上的RAM

图1.7展示了PC系统中各类存储器的逻辑连接情况。

在这里插入图片描述

1.15 内存地址空间

上述的那些存储器,在物理上是独立的器件,但是在以下两点上相同。

  • 都和CPU的总线相连。
  • CPU对它们进行读或写的时候都通过控制线发出内存读写命令。

这也就是说,CPU在操控它们的时候,把它们都当作内存来对待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。
图1.8展示了CPU将系统中各类存储器看作一个逻辑存储器的情况。
在这里插入图片描述

在图1.8中,所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。

我们在基于一个计算机硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况。

比如,我们要向显示器输出一段信息,那么必须将这段信息写到显存中,显卡才能将它输出到显示器上。要向显存中写入数据,必须知道显存在内存地址空间中的地址。

不同的计算机系统的内存地址空间的分配情况是不同的,图1.9 展示了8086PC机内存地址空间分配的基本情况。

在这里插入图片描述

图1.9告诉我们,从地址09FFFF的内存单元中读取数据,实际上就是在读取主随机存储器中的数据;向地址A0000BFFFF的内存单元中写数据,就是向显存中写入数据,这些数据会被显示卡输出到显示器上;我们向地址C0000~ FFFFF的内存单元中写入数据的操作是无效的,因为这等于改写只读存储器中的内容。

第二章

2.1 通用寄存器

8086CPU的所有寄存器都是16位的,可以存放两个字节。

AX、BX、CX、DX这四个寄存器通常用来存放一般性的数据,被称为通用寄存器

以AX为例,寄存器的路径如图2.1所示:

在这里插入图片描述
8086CPU的AX、BX、CX、DX这4个寄存器都可分为两个可独立使用的8位寄存器来用:

  • AX可分为AH和AL;
  • BX可分为BH和BL;
  • CX可分为CH和CL;
  • DX可分为DH和DL。

例如图2.2:数据18

二进制表示:10010

在寄存器AX中的存储:

在这里插入图片描述
图2.216位数据在寄存器中的存放情况

以AX为例,8086CPU的16位寄存器分为两个8位寄存器的情况如图2.3所示。
在这里插入图片描述
AX的低8位(0位-7位)构成了AL寄存器,高8位(8位~15位)构成了AH寄存器。

AH和AL寄存器是可以独立使用的8位寄存器。

2.2 字在寄存器中的存储

8086CPU可以一次性处理以下两种尺寸的数据。

字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节,如图2.5所示。

在这里插入图片描述
一个字可以存在一个 16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。

例如:一个字型数据20000, 存在AX寄存器中,在AH中存储了它的高8位,在AL中存储了它的低8位。AH和AL中的数据,既可以看成是一个字型数据的高8位和低8位,这个字型数据的大小是20000;又可以看成是两个独立的字节型数据,它们的大小分别是78和32。

2.3 几条汇编指令

通过汇编指令控制CPU进行工作,如下表2.1:
在这里插入图片描述
为了使具有高级语言基础的读者更好地理解指令的含义,有时会用文字描述和高级语言描述这两种方式来描述一条汇 编指令的含义。

在写一条汇编指令或一个寄存器的名称时不区分大小写。

如: mov ax,18和MOV AX,18的含义相同; bx和BX的含义相同。

接下来看一下CPU执行表2.2中所列的程序段中的每条指令后,对寄存器中的数据进行的改变。
在这里插入图片描述
在这里插入图片描述

问题 2.1

指令执行后AX中的数据为多少?

程序段中的最后一条指令add ax,bx,在执行前ax和bx中的数据都为8226H,相加后所得的值为: 1044CH, 但是ax为16位寄存器,只能存放4位十六进制的数据,所以最高位的1不能在ax中保存,ax 中的数据为: 044CH。

表2.3中所列的一段程序的执行情况。

在这里插入图片描述

问题 2.2

指令执行后AX中的数据为多少?

分析:
程序段中的最后一条指令add al,93H, 在执行前,al 中的数据为C5H,相加后所得的值为: 158H,但是al为8位寄存器,只能存放两位十六进制的数据,所以最高位的1丟失,ax中的数据为: 0058H。

注意,此时a1 是作为一个独立的8位寄存器来使用的,和ah没有关系,CPU在执行这条指令时认为ah 和al是两个不相关的寄存器。不要错误地认为,诸如add al,93H 的指令产生的进位会存储在ah中,add al,93H进行的是8位运算。

在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的,例如:

mov ax, bx
mov bx, CX
mov ax, 18H
mov al, 18H
add ax, bx .
add ax, 20000

等都是正确的命令,而:

mov ax,bl(在8位寄存器和16位寄存器之间传送数据)
mov bh, ax(在16位寄存器和8位寄存器之间传送数据)
mov al, 20000(8位寄存器最大可存放值为255的数据)
add al, 100H(将一个高于8位的数据加到一个8位寄存器中)

等都是错误的指令,错误的原因都是指令的两个操作对象的位数不一致。

检测点 2.1

(1) 写出每条汇编指令执行后相关寄存器中的值。
mov ax, 62627 AX=F4A3H
mov ah, 31H AX=31A3H
mov al, 23H AX=3123H
add ax, ax AX=6246H
mov bx, 826CH BX=826CH
mov cx, ax CX=6246H
mov ax, bx AX= 826CH
add ax, bx AX=04D8H
mov al, bh AX=0482H
mov ah,bl AX=6C82H
add ah, ah AX=D882H
add al,6 AX=D888H

add al,al AX=D810H

mov ax,cx AX=6246H

(2)只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方。
mov ax, 2 AX=2
add ax, ax AX=4
add ax, ax AX=8
add ax, ax AX=16

2.4 物理地址

CPU访问内存单元时,要给出内存单元的地址。

物理空间:所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址。

CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。

在CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址。不同的CPU可以有不同的形成物理地址的方式。

2.5 16位结构的CPU

什么是16位结构的CPU?

概括地讲,16 位结构(16位机、字长为16位等常见说法,与16位结构的含义相同)描述了一个CPU具有下面几方面的结构特性。
●运算器一次最多可以处理16位的数据;
●寄存器的最大宽度为16位;
●寄存器和运算器之间的通路为16位。

8086是16位结构的CPU,这也就是说,在8086内部,能够一次性处理、 传输、暂时存储的信息的最大长度是16位的。

内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放,对于16位CPU,能一次性处理、传输、暂时存储16位的地址。

2.6 8086CPU给出物理地址的方法

8086CPU采用一种在内部用两个16位地址合成的方法来形成一个 20位的物理地址。

8086CPU相关部件的逻辑结构如图2.6所示。
在这里插入图片描述

如图2.6所示,当8086CPU要读写内存时:

  1. CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
  2. 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
  3. 地址加法器将两个16 位地址合成为一个20位的物理地址;
  4. 地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
  5. 输入输出控制电路将20位物理地址送上地址总线;
  6. 20位物理地址被地址总线传送到存储器。

地址加法器采用物理地址=段地址x16+偏移地址的方法用段地址和偏移地址合成物理地址。

例如,8086CPU要访问地址为123C8H的内存单元,此时,地址加法器的工作过程如图2.7所示(图中数据皆为十六进制表示)。

在这里插入图片描述

2.7 “段地址x16+偏移地址=物理地址”的本质含义

“段地址x 16+偏移地址=物理地址”的本质含义是:

CPU在访问内存时,用一个基础地址(段地址x16)和一一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。

更一般地说,8086CPU的这种寻址功能是“基础地址+偏移地址=物理地址”寻址模式的一种具体实现方 案。8086CPU 中,段地址x16可看作是基础地址。

2.8 段的概念

我们注意到,“ 段地址”这个名称中包含着“段”的概念。这种说法可能对一些学习者产生了误导,使人误以为内存被划分成了一个一个的段,每一个段有一个段地址。

其实,内存并没有分段,段的划分来自于CPU,由于8086CPU用“基础地址(段地址x 16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。

如图2.9 所示,我们可以认为:地址10000H~100FFH 的内存单元组成一个段,该段的起始地址(基础地址)为10000H, 段地址为1000H, 大小为100H; 我们也可以认为地址10000H~1007FH、 10080H~100FFH的内存单元组成两个段,它们的起始地址(基础地址)为: 10000H 和10080H,段地址为: 1000H 和1008H,大小都为80H。

注意:段地址x16必然是16 的倍数,所以一个段的起始地址也一定是16的倍数;偏移地址为16位,16 位地址的寻址能力为64KB,所以一个段的长度最大为64KB。

检测点 2.2

(1)给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为0010H1000FH

解题过程:
物理地址=SA16+EA
EA的变化范围为Oh~ ffffh
物理地址范围为(SA
16+0h)~ (SA16+fffh)
现在SA=0001h,那么寻址范围为
(0001h * 16+0h)~ (0001h
16+ffffh)
=0010h~ 1000fh

(2)有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为1001H,最大为2000H
解题过程:
物理地址=SA*16+EA
20000h= SA *16+EA
SA= (20000h-EA) /16=2000h-EA/16
EA取最大值时,SA=2000h-ffffh/16=1001h, SA为最小值
EA取最小值时,SA=2000h-0h/16=2000h, SA为最大值

2.9 段寄存器

8086CPU在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址。

这里,需要看一下,是什么部件提供段地址。段地址在8086CPU的段寄存器中存放。8086CPU 有4个段寄存器: CS、DS、SS、ES。

当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。

2.10 CS和IP

CS和IP是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。

CS为代码段寄存器,IP 为指令指针寄存器。

在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU 将从内存Mx16+N单元开始,读取一条指令并执行。
也可以这样表述: 8086 机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。

图2.10展示了8086CPU读取、执行指令的工作原理(图中只包括了和所要说明的问题密切相关的部件,图中数字都为十六进制)。
在这里插入图片描述
图2.10 说明如下。

(1)8086CPU当前状态:CS中的内容为2000H,IP中的内容为0000H;
(2)内存 20000H~20009H单元存放着可执行的机器码;
(3)内存20000H~ 20009H单元中存放的机器码对应的汇编指令如下。
地址: 20000H~20002H,内容: B8 23 01, 长度: 3Byte, 对应汇编指令: mov ax,0123H
地址: 20003H ~20005H,内容: BB 03 00,长度: 3Byte, 对应汇编指令: mov bx,0003H
地址: 20006H~20007H,内容: 89 D8,长度: 2Byte,对应汇编指令:mov ax,bx
地址: 20008H~20009H,内容: 01 D8,长度: 2Byte,对应汇.编指令: add ax,bx

以图2.10描述的情况为初始状态,展示了8086CPU读取、执行一条指令的过程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面的一组图(图2.20 ~图2.26), 以图2.19的情况为初始状态,展示了8086CPU继续读取、执行3条指令的过程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过上面的过程展示,8086CPU 的工作过程可以简要描述如下。

(1)从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
(2) IP=IP+所读取指令的长度, 从而指向下一条指令;
(3)执行指令。转到步骤(1),重复这个过程。

在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS= FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H 单元中的指令是8086PC机开机后执行的第一条指令。

2.11 修改CS、IP的指令

在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。

CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容来控制CPU执行目标指令。

我们如何改变CS、IP 的值呢?

显然,8086CPU 必须提供相应的指令。

其实,8086CPU大部分寄存器的值,都可以用mov指令来改变,mov指令被称为传送指令。

但是,mov指令不能用于设置CS. IP的值,原因很简单,因为8086CPU没有提供这样的功能。8086CPU为CS、IP提供了另外的指令来改变它们的值。

能够改变CS.IP的内容的指令被统称为转移指令

我们现在介绍一个最简单的可以修改CS、IP 的指令: jmp 指令。

若想同时修改CS、IP 的内容,可用形如“jmp 段地址:偏移地址”的指令完成,如

jmp 2AE3:3,执行后: CS=2AE3H,IP=0003H, CPU 将从2AE33H处读取指令。
jmp 3:0B16,执行后: CS=0003H, IP=0B16H, CPU将从00B46H处读取指令。

“jmp段地址:偏移地址”指令的功能为:用指令中给出的段地址修改CS,偏移地址修改IP。

若想仅修改IP的内容,可用形如“jmp 某一合法寄存器”的指令完成,如

jmp ax,指令执行前: ax=1000H, CS=2000H,IP=0003H
指令执行后: ax=1000H,CS=2000H, IP=1000H
jmp bx,指令执行前: bx=0B16H,CS=2000H, IP=0003H
指令执行后: bx=0B16H,CS=2000H, IP=0B16H

“jmp某一合法寄存器”指令的功能为:用寄存器中的值修改IP。

jmp ax,在含义上好似: mov IP,ax。

问题 2.3

内存中存放的机器码和对应的汇编指令情况如图2.27所示,设CPU初始状态:
CS= 2000H, IP=0000H, 请写出指令执行序列。

在这里插入图片描述
分析:

(1) 当前CS=2000H, IP= 0000H, 则CPU从内存2000HX 16+0=20000H 处读取指令,读入的指令是: B8 22 66(mov ax,6622H),读入后IP=IP+3=0003H;
(2) 指令执行后,CS= 2000H, IP= 0003H, 则CPU 从内存2000H X 16+0003H=20003H处读取指令,读入的指令是: EA 03 00 00 10(jmp 1000:0003), 读入后IP=IP+5=0008H;
(3) 指令执行后,CS=1000H, IP= 0003H, 则CPU从内存1000H X 16+0003H= 10003H处读取指令,读入的指令是: B8 00 00(mov ax,0000),读入后IP=IP+3=0006H;
(4) 指令执行后,CS=1000H, IP= 0006H, 则CPU 从内存1000H X 16+0006H= 10006H处读取指令,读入的指令是: 8B D8(mov bx,ax),读入后IP=IP+2=0008H;
(5) 指令执行后,CS=1000H, IP= 0008H, 则CPU从内存1000H X 16+0008H= 10008H处读取指令,读入的指令是: FF E3(jmp bx),读入后IP= =IP+2=000AH;
(6)指令执行后, CS= 1000H,IP= 0000H,CPU从内存10000H处读取指…

经分析后,可知指令执行序列为:
(1) mov ax,6622H
(2) jmp 1000:3
(3) mov ax,0000
(4) mov bx,ax
(5) jmp bx
(6) mov ax,0123H
(7)转到第3步执行

2.12 代码段

对于8086PC 机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N≤64KB)的一组代码, 存在一组地址连续、起始地址为16的倍数的内存单元中,我们可以认为,这段内存是用来存放代码的,从而定义了一个代码段。比如,将:

mov ax, 0000 (B8 00 00)
add ax, 0123H (052301)
mov bx, ax (8B D8)
jmp bx (FE E3)

这段长度为10个字节的指令,存放在123B0H~123B9H 的一组内存单元中,我们就可以认为,123B0H~ 123B9H这段内存是用来存放代码的,是一个代码段,它的段地址为123BH,长度为10个字节。

由于CPU只认被CS:IP指向的内存单元中的内容为指令。所以,要让CPU执行我们放在代码段中的指令,必须要将CS:IP 指向所定义的代码段中的第一条指令 的首地址。对于上面的例子, 我们将一段代码存放在 123B0H ~123B9H内存单元中, 将其定义为代码段,如果要让这段代码得到执行,可设CS=123BH、IP=0000H。

检测点 2.3

下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax, bx
sub ax, ax
jmp ax

答:一共修改四次
第一次:读取mov ax,bx之后
第二次:读取sub ax,ax之后
第三次:读取jmp ax之后
第四次:执行jmp ax修改IP
最后IP的值为0000H,因为最后ax中的值为0000H,所以IP中的值也为
0000H

实验1查看CPU和内存,用机器指令和汇编指令编程

1.预备知识: Debug的使用
我们以后所有的实验中,都将用到Debug程序,首先学习一下它的主要用法。
(1)什么是Debug?
Debug是DOS、Windows都提供的实模式(8086方式)程序的调试工具。使用它,可以查看CPU各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。

(2)我们用到的Debug功能。
●用Debug的R命令查看、改变CPU寄存器的内容;
●用Debug的D命令查看内存中的内容;
●用Debug的E命令改写内存中的内容;
●用Debug的U命令将内存中的机器指令翻译成汇编指令;
●用Debug的T命令执行一条机器指令;
●用Debug的A命令以汇编指令的格式在内存中写入一条机器指令。

Debug的命令比较多,共有20多个,但这6个命令是和汇编学习密切相关的。在以后的实验中,我们还会用到一个P命令。
(3)进入Debug。
Debug是在DOS方式下使用的程序。我们在进入Debug前,应先进入到DOS方式。用以下方式可以进入DOS。
①重新启动计算机,进入DOS方式,此时进入的是实模式的DOS。
②在Windows中进入DOS方式,此时进入的是虚拟8086模式的DOS。
下面说明在Windows 2000 中进入Debug的一种方法,在其它Windows系统中进入的方法与此类似。

选择**[开始]菜单中的[运行]命令,如图2.28 所示,打开[运行]对话框,如图2.29所示,在文本框中输入“command”后,单击[确定]**按钮。
在这里插入图图片描述
(4)用R命令查看、改变CPU寄存器的内容。
我们已经知道了AX、BX、CX、DX、CS、IP这6个寄存器,现在看一下它们之中的内容,如图2.31所示。其他寄存器如SP、BP、SI、DI、DS、ES、SS、标志寄存器等。
在这里插入图片描述
图2.31 使用R命令查看CPU中各个寄存器中的内容

还可以用R命令来改变寄存器中的内容,如图2.32所示。
在这里插入图片描述
图2.32用R命令 修改寄存器AX中的内容

在图2.33中,一进入 Debug,用R命令查看,CS:IP 指向0B39:0100,此处存放的机
器码为40,对应的汇编指令是INC AX;
接着,用R命令将IP修改为200,则CS:IP 指向0B39:0200,此处存放的机器码为
5B,对应的汇编指令是POP BX;
接着,用R命令将CS修改为ff00, 则CS:IP指向ff00:0200, 此处存放的机器码为
51,对应的汇编指令是PUSH CX。
在这里插入图片描述
图2.33 用R命令修改CS和IP中的内容

(5)用Debug的D命令查看内存中的内容。
如果我们想知道内存10000H 处的内容,可以用“d段地址:偏移地址”的格式来查
看,如图2.34所示。
在这里插入图片描述
图2.34 用D命令查看内存1000:0处的内容

要查看内存10000H 处的内容,首先将这个地址表示为段地址:偏移地址的格式,可以
是1000:0,然后用“d 1000:0”列出1000:0处的内容。
使用“d段地址:偏移地址”的格式,Debug将列出从指定内存单元开始的128个内
存单元的内容。图2.34 中,在使用d 1000:0后,Debug列出了1000:0~1000:7F 中的内容。
使用D命令,Debug 将输出3部分内容(如图2.34所示)。.
我们使用d 1000:9查看1000:9处的内容,Debug 将怎样输出呢?如图2.35所示。
在这里插入图片描述
图2.35查看 1000:9处的内容
在一进入Debug后,用D命令直接查看,将列出Debug预设的地址处的内容,如图2.36所示。

在这里插入图片描述
图2.36 列出Debug预设的地址处的内容
在使用“d段地址:偏移地址”之后,接着使用D命令,可列出后续的内容,如图2.37所示。
也可以指定D命令的查看范围,此时采用“d段地址:起始偏移地址结尾偏移地址”的格式。比如要看1000:0~ 1000:9中的内容,可以用“d 1000:09”实现,如图2.38所示。
在这里插入图片描述
图2.37 列出后续的内容 在这里插入图片描述
图2.38 查看1000:0~1000:9单元中的内容
如果我们就想查看内存单元10000H中的内容,可以用图2.39 中的任何一种方法看
到,因为图中的所有“段地址:偏移地址”都表示了10000H 这-一物理地址。
在这里插入图片描述
图2.39用3种不同的段地址和偏移地址查看同一个物理地址中的内容

(6)用Debug的E命令改写内存中的内容。
可以使用E命令来改写内存中的内容,比如,要将内存1000:0 ~1000:9单元中的内容
分别写为0、1、2、3、4、5、6、7、8、9,可以用“e起始地址 数据 数据 数据…”.的格式来进行,如图2.40所示。

在这里插入图片描述
图2.40用E命令修改从1000:0开始的10个单元的内容

(7) 用E命令向内存中写入机器码,用U命令查看内存中机器码的含义,用T命令
执行内存中的机器码。
在这里插入图片描述
图2.43 用E命令向内存中写入字符串
如何向内存中写入机器码呢?我们知道,机器码也是数据,当然可以用E命令将机器
码写入内存。比如我们要从内存1000:0单元开始写入这样一段机器码:
机器码 对应的汇编指令
b80100 mov ax, 0001
b90200 mov cx, 0002
01c8 add ax, cx
可用如图2.44中所示的方法进行。
在这里插入图片描述
图2.44用E命令将机器码写入内存
如何查看写入的或内存中原有的机器码所对应的汇编指令呢?可以使用U命令。比如可以用U命令将从1000:0开始的内存单元中的内容翻译为汇编指令,并显示出来,如图2.45所示。
在这里插入图片描述
图2.45 用U命令将内存单元中的内容翻译为汇编指令显示

图2.45中,首先用E命令向从1000:0开始的内存单元中写入了8个字节的机器码;然后用D命令查看内存1000:0 1000:1f 中的数据(从数据的角度看一下写入的内容);最后用U命令查看从1000:0开始的内存单元中的机器指令和它们所对应的汇编指令。
如何执行我们写入的机器指令呢?使用Debug 的T命令可以执行一条或多条指令,
简单地使用T命令,可以执行CS:IP指向的指令,如图2.46所示。
在这里插入图片描述
接着图2.46,我们可以继续使用T命令执行下面的指令,如图2.47所示。
在这里插入图片描述
(8)用Debug的A命令以汇编指令的形式在内存中写入机器指令。
前面我们使用E命令写入机器指令,这样做很不方便,最好能直接以汇编指令的形式
写入指令。为此,Debug 提供了A命令。A 命令的使用方法如图2.48所示。
在这里插入图片描述
图2.48中,首先用A命令,以汇编语言向从1000:0开始的内存单元中写入了几条指令,然后用D命令查看A命令的执行结果。可以看到,在使用A命令写入指令时,我们输入的是汇编指令,Debug 将这些汇编指令翻译为对应的机器指令,将它们的机器码写入内存。
使用A命令写入汇编指令时,在给出的起始地址后直接按Enter键表示操作结束。

如图2.49中,简单地用A命令,从一个预设的地址开始输入指令。
在这里插入图片描述
图2.49 从一个预设的地址开始输入指令

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

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号单元中。

我们提出字单元的概念:字单元,即存放-一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。

问题 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。

3.2 DS和[address]

CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取10000H单元的内容,可以用如下的程序段进行。

mov bx, 1000H
mov ds,bx
mov al, [0]

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

前面我们使用mov指令,可完成两种传送:①将数据直接送入寄存器;②将一个寄存器中的内容送入另一个寄存器。

也可以使用mov 指令将一个内存单元中的内容送入一个寄存器中。从哪一个内存单元送到哪一个寄存器中呢?在指令中必须指明。寄存器用寄存器名来指明,内存单元则需用内存单元的地址来指明。显然,此时mov指令的格式应该是: mov 寄存器名,内存单元地址。

“[…]”表示一个内存单元,“[…]” 中的0表示内存单元的偏移地址。

如何用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" 这样的指令来完成,从理论上讲,我们可以用相似的方式: 1mov ds,1000H, 来将1000H 送入ds。

可是8086CPU 不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以mov ds, 1000H这条指令是非法的。那么如何将1000H送入ds呢?只好用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器,如bx, 再将bx中的内容送入ds。

问题 3.2

写几条指令,将al中的数据送入内存单元10000H中。
从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中的值。

mov ax, 1000H
mov ds, ax
mov ax, [0]
mov bx, [2]
mov cx, [1]
add bx, [1]
add cx, [2]
在这里插入图片描述

进行单步跟踪,看一下每条指令执行后相关寄存器中的值,见表3.1。
在这里插入图片描述

问题3.4

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

在这里插入图片描述

mov ax, 1000H
mov ds, ax
mov ax, 11316
mov [0] , ax
mov bx, [0]
sub bx, [2]
mov [2] ,bx

进行单步跟踪,看一下每条指令执行后相关寄存器或内存单元中的值,见表3.2。
在这里插入图片描述

3.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 .

(1) 既然有“mov 段寄存器,寄存器”,从寄存器向段寄存器传送数据,那么也应该有“mov寄存器,段寄存器”,从段寄存器向寄存器传送数据。一个合理的设想是:8086CPU内部有寄存器到段寄存器的通路,那么也应该有相反的通路。

进入Debug,进行验证,如图3.4:
在这里插入图片描述
​ 图3.4 试验mov ax,ds

图3.4中,用A命令在一个预设的地址0B39:0100处,用汇编的形式mov ax,ds写入指令,再用T命令执行,可以看到执行的结果,段寄存器ds中的值送到了寄存器ax中。通过验证我们知道,“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 单元存放0BH,1000:0 单元存放39H。

(3)“mov段寄存器,内存单元”也应该可行,比如我们可以用10000H 处存放的字型数据设置ds(即将10000H处存放的字型数据送入ds),指令如下。

mov ax, 1000H
mov ds, ax
mov ds, [0]

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 [O],ax

3.5数据段

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

如何访问数据段中的数据呢?

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

比如,将123B0H~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中

3.6 栈

栈是一种具有特殊的访问方式的存储空间。

它的特殊性就在于,最后进入这个空间的数据,最先出去。

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

入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为: LIFO(Last In First Out,后进先出)。

3.7 CPU提供的栈机制

现今的CPU中都有栈的设计,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位。

这不禁让我们想起另外一个讨论过的问题,就是,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 此时指向新栈项。
图3.10描述了8086CPU 对push指令的执行过程。
在这里插入图片描述
从图中我们可以看出,8086CPU 中,入栈时,栈顶从高地址向低地址方向增长。

问题 3.6

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

分析:
在这里插入图片描述
将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 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 栈顶超界的问题

我们现在知道,8086CPU用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次popax后,从栈中弹出8个字,栈空,SS:SP 指向10020H;
再次执行pop ax: sp sp+2,SS:SP 指向10022H, 栈顶超出了栈空间。此后,如果再执行push指令,10020H、 10021H 中的数据将被覆盖。

上面描述了执行push、 pop指令时,发生的栈顶超界问题。可以看到,当栈满的时候再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。

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

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

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页