<h1 align='center'>汇编语言
问题:内存存储的阅读顺序。从0往后读。还是分为段,两个字节的,低位为高位,高位为低位
第一章 基础知识
1.1 机器语言
1.2 汇编语言的产生
1.3 汇编语言的组成
1.4 存储器
1.5 指令和数据
1.6 存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
电子计算机的最小信息单位是bit(比特、位)。
一般存储单元可以存储一个Byte(字节),即8bit,8个二进制位。1个存储单元可以存1Byte。
1.7 CPU对存储器的读写
存储单元的编号可以看作是地址信息(例如:地址11,就是编号为11的存储单元)。
CPU先读存储单元的“编号”再读取数据。
CPU在读数据时还需要指明:对哪一个器件进行操作、进行哪种操作(读|写)。
CPU读取数据时进行的3类信息的交互:
- 存储单元的地址;(地址信息)
- 器件的选择,读或写的命令;(控制信息)
- 读或写的数据。(数据信息)
计算机处理、传输的信息都是电信号,电信号通过导线传送。
专门连接CPU和其他芯片的导线,通常成为总线。(一根根导线的集合)
总线从逻辑上分为3类:
- 地址总线、控制总线、数据总线
读取数据过程举例:
- CPU通过地址线发出地址信息;
- CPU通过控制线发出读(写的过程类似)的命令,选中存储器芯片,并通知它,将要进行操作;
- 存储器将对应地址的数据通过数据线进行传入CPU(CPU将数据通过数据线送入内存中)。
1.8 地址总线
地址线 ? 地址总线 :
一根导线可以传送的稳定状态只有两种,高电平或低电平(1、0)。(1根导线就是1个二进制)
一个CPU有N根地址线,则这个CPU的地址总线的宽度为N。这个CPU最多可以寻找2的N次方个内存单元(存储单元)。
我的疑问:(xx线跟xx总线不是同一个东西)(书中混用了)
<div style="display:flex; justify-content: space-between"> <img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909172701664.png" style="width: 33%"> <img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909172704468.png" style="width: 33%"> <img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909185934460.png" style="width: 33%"> </div>
1.9 数据总线
数据总线的宽度决定了CPU和外界的数据传送速度。
8根数据线一次可以传送1个字节的数据。16根2字节。
8086CPU有16根数据线,可一次传送16位数据。16根数据线,16位信息,每4位表示1个字符,即1个16进制的数据。
8088CPU有8根数据线,可一次传送8位数据。16进制的输出需要传送2次才行。
1.10 控制总线
到底几根控制线控制一个信号?
- 控制总线的宽度决定了CPU对外部器件的控制能力
- 内存读或写命令是由几根控制线综合发出的。有一根称为“读信号输出”的控制线,输出低电平;有一根称为“写信号输出”的。
小结
- 汇编指令是机器指令的助记符,同机器指令一一对应。
- 每一种CPU都有自己的汇编指令集。
- CPU可以直接使用的信息在存储器中存放。
- 在存储器中指令和数据没有任何区别,都是二进制信息。
- 存储单元从零开始顺序编号。
- 一个存储单元可以存储8个bit,即8位二进制数。
- 1Byte = 8bit 1KB = 1024B 1MB = 1024KB 1GB = 1024MB
- 每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出3种总线的宽度标志了这个CPU的不同方面的性能:
- 地址总线的宽度决定了CPU的寻址能力;
- 数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量;
- 控制总线的宽度决定了CPU对系统中其他器件的控制能力;
- 说明:从功能的角度介绍3类总线,对实际的连接情况不做讨论。
练习1.1
巧计:$2^{10}=1024$

- 1个CPU的寻址能力位8KB,那么它的地址总线的宽度为____。
- 1KB的存储器有###个存储单元。存储单元的编号从###到###。
- 1KB的存储器可以存储###个bit,###个Byte。
- 1GB、1MB、1KB分别是###Byte。
- 8080、8088、80286、80386的地址总线宽度分别为16根、20根、24根、32根则它们的寻址能力分别为:###KB、###MB、###MB、###GB。
- 8080、8088、8086、80286的数据总线宽度为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:###B、###B、###B、###B、###B。
- 从内存中读取1024字节的数据,8086至少要读###次,80386至少要读###次。
- 在存储器中,数据和程序以###形式存放。
疑问:地址总线是怎么找到那么多存储单元的?N=32,那么就能找到2^32个对应的存储单元,这些存储单元都是独立的?这个数量是不是太庞大了?
- 答案
- 13
- 2^10 0 2^10-1
- 2^13 2^10
- 2^30 2^20 2^10
- 2^6 1 2^4 2^2
- 1 1 2 2 4
- 2^9 2^8
- 二进制
1.11 内存地址空间(概述)
1.12 主板
- 主板上有核心器件和主要器件。这些器件通过总线相连。
- CPU、存储器、外围芯片组、扩展插槽等。
- 扩展插槽上一般插有RAM内存条和各类接口卡。
1.13 接口卡
- 所有可用程序控制其工作的设备,必须受到CPU的控制。
- CPU对外部设备(如显示器、打印机等)都不能直接控制,直接控制这些设备进行工作的是插在扩展插槽上的接口卡。
- CPU通过直接控制接口卡间接控制外设。
- 总结:CPU通过总线向接口卡发送命令,接口卡依据CPU的命令控制外设进行工作。
1.14 各类存储器芯片
物理连接上看是独立的、不同的器件。
从读写属性上看分为两类:
- 随机存储器(RAM):可读可写,带电存储,关机后存储的内容丢失。
- 只读存储器(ROM):只读不写,关机后内容不丢失。(系统出场自带的固定程序。蓝屏程序)
从功能和连接上又分为:
- 随机存储器:存放CPU使用的大部分程序和数据,主随机存储器一般由两个位置上的RAM组成,装在主板上的RAM和插在扩展插槽上的RAM。
- 装有BIOS(Basic Input/Output System)的ROM:BIOS是由主板和各类接口卡(如网卡、显卡等)厂商提供的软件系统。
- 接口卡上的RAM:典例:显示卡上的RAM,一般称为显存。大批量数据进行暂时存储。显示器频繁显示数据。
示意图:
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909193828663.png" alt="image-20250909193828663" style="zoom: 50%;" />
1.15 内存地址空间
上文的存储器在物理上是独立的器件,但是在以下两点上相同:
- 都和CPU的总线相连
- CPU对它们进行读或写的时候都通过控制线发出内存读写指令
CPU在操纵存储器的时候,都把它们当作内存来对待。
总的看作一个由若干存储单元组成的逻辑存储器。这个逻辑存储器就是所说的内存地址空间。(我们面对的就是内存地址空间)
示意图:
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909195617547.png" alt="image-20250909195617547" style="zoom:50%;" />
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器占有一个地址段,即一段地址空间。(每个存储器分配一个地址空间,范围)
内存地址空间的大小手CPU地址总线宽度的限制。
在基于硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况。读写数据时必须知道第一个单元的地址和最后一个单元的位置,才能保证在预期的存储器上进行。
不同的计算机系统的内存地址空间的分配情况是不同的。
总结
- 系统中的所有存储器中的存储单元都处于一个统一的逻辑存储器中。
- 存储器的容量受CPU寻址能力的限制。
- 这个逻辑存储器即是我们所说的内存地址空间。
第二章 寄存器
说明:地址后面+H结尾的,表示16进制数据
一个典型的CPU由运算器、控制器、寄存器等器件构成。
这些器件靠内部总线相连。(相对的,外部总线)
在CPU中:
- 运算器进行信息处理;
- 寄存器进行信息存储;
- 控制器控制各种器件进行工作;
- 内部总线连接各种器件,进行数据传送。
对于汇编程序员来说,CPU中的主要部件是寄存器。
寄存器是程序员可以用指令读写的部件。通过改变寄存器中的内容来实现对CPU的控制。
不同的CPU,寄存器的个数、结构是不相同的。
8086CPU有14个寄存器。这些寄存器是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。
2.1 通用寄存器
- 8086CPU的所有寄存器都是16位的,可以存放2个字节。
- AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,被称为通用寄存器。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909201627340.png" alt="image-20250909201627340" style="zoom:67%;" />
问:1个16位寄存器所能存储的数据的最大值为多少?0~65535
8086CPU的上一代CPU中的寄存器都是8位的,为了保证兼容,8086CPU的AX、BX、CX、DX这4个寄存器都可分为两个可独立使用的8位寄存器使用。
- AX可分为AH和AL;
- BX可分为BH和BL;
- CX可分为CH和CL;
- DX可分为DH和DL。
AX的低8位(0~7)构成了AL寄存器,高8位(8~15)构成了AH寄存器。L=low,H=high。
问:1个8位寄存器所能存储的数据的最大值为多少?0~255
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909203418927.png" alt="image-20250909203418927" style="zoom: 67%;" />
疑问:为什么图中排号是从右到左?但是存储的数据还是从左到右进行填充。
2.2 字在寄存器中的存储
- 出于对兼容性的考虑,8086CPU可以一次性处理以下两种尺寸的数据。
- 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
- 字:记为word,一个字由两个字节组成,分别称为这个字的高位字节和低位字节。
- 一个字可以存放在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。
关于数制的讨论
任何数据在计算机中都是以二进制的形式进行存储。但是为了描述不同的问题,经常将它们用其它的进制进行表示。
十六进制数的一位相当于二进制的四位。如
0100111000100000可表示为:4(0100)、E(1110)、2(0010)、0(0000)十六进制数的一位相当于八进制的两位。
2.3 几条汇编指令
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909204504597.png" alt="image-20250909204504597" style="zoom:67%;" />
- 指令不区分大小写。
问题 2.1
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909204651344.png" alt="image-20250909204651344" style="zoom:67%;" />
问:最后AX的数据为多少?
答案:044CH
问题 2.2
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909204844327.png" alt="image-20250909204844327" style="zoom:67%;" />
- 问:最后AX的值为多少?
- 答案:0058H
注意
- 寄存器运算进位的问题:
- 非最高位正常进位。最高位无法进位,舍去。(寄存器运算时支持的最高位无法进位)
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909210015405.png" alt="image-20250909210015405" style="zoom:67%;" />
- 在进行数据传送或运算时,要注意两个指令的两个操作对象的位数应当是一致的。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250909210101374.png" alt="image-20250909210101374" style="zoom:67%;" />
- 说明:
- 如果操作对象是常数,则数据类型可以“隐式转换”,但是不能超过支持的最大范围。
- 如果操作对象是变量(寄存器),则必须保持相同的数据类型(位数相同)。
检测点 2.1
写出每条汇编指令执行后相关寄存器中的值。
mov ax,62627 AX=_________ mov ah,31H AX=_________ mov al,23H AX=_________ add ax,ax AX=_________ mov bx,826CH BX=_________ mov cx,ax CX=_________ mov ax,bx AX=_________ add ax,bx AX=_________ mov al,bh AX=_________ mov ah,bl AX=_________ add ah,ah AX=_________ add al,6 AX=_________ add al,al AX=_________ mov ax,cx AX=_________只能使用目前学过的汇编指令,最多使用 4 条指令,编程计算 2 的 4 次方。

答案:
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- assembly
MOV AX,2 # 或 MOV AX,2H ADD AX,AX ADD AX,AX ADD AX,AX
2.4 物理地址
- 所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址称为物理地址。
2.5 16位结构的CPU
- 16位结构的CPU具有下面几方面的结构特性:
- 运算器一次最多可以处理16位的数据;
- 寄存器的最大宽度为16位;
- 寄存器和运算器之间的通路为16位。
- 8086CPU是16位结构的CPU,也就是说,在8086内部,能够一次性处理、传输、暂时存储的信息的最大长度是16位的。
- 内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放。
2.6 8086CPU给出物理地址的方法
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。
8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。从内部结构来看,它只能送出16位的地址,表现出的寻址能力只有64KB。
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
- CPU 中的相关部件提供两个 16 位的地址,一个称为段地址,另一个称为偏移地址;
- 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
- 地址加法器将两个 16 位地址合成为一个 20 位的物理地址;
- 地址加法器通过内部总线将 20 位物理地址送入输入输出控制电路;
- 输入输出控制电路将 20 位物理地址送上地址总线;
- 20 位物理地址被地址总线传送到存储器。
地址加法器采用
物理地址=段地址×16+偏移地址的方法用段地址和偏移地址合成物理地址。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250910140836413.png" alt="image-20250910140836413" style="zoom:67%;" />
由段地址×16引发的讨论
- 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
- 一个X进制的数据左移1位,相当于乘以X。
2.7 ”段地址×16+偏移地址=物理地址“的本质含义
本质含义是:CPU在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址
更一般的说,8086CPU的这种寻址能力是”基础地址+偏移地址=物理地址“寻址模式的一种具体实现方案。段地址×16可看作基础地址
形象理解:先找到个大概地址,再通过偏移地址进行精准定位。
2.8 段的概念
将连续的地址(逻辑存储器),在概念上进行分段。
理解:
- 我们可以认为:地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H;
- 我们也可以认为:地址10000H~1007FH、10080H~100FFH的内存单元组成两个段,它们的起始地址(基础地址)为:10000H和10080H,段地址为:1000H和1008H,大小都为80H。
有两点需要注意:
- 段地址×16必然是16的倍数,所以一个段的起始地址也一定是16的倍数;
- 偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB。
结论:
- CPU可以用不同的段地址和偏移地址形成同一个物理地址。(段地址:SA,偏移地址:EA)
- 偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64KB个内存单元。
描述规范:
- ”数据在21F60H“内存单元中。”对于8086PC机一般不这样讲。
- 数据存在内存2000:1F60单元中。
- 数据存在内存的2000H段中的1F60H单元中。
可以根据需要,将地址连续、起始地址为16为16的倍数的一组内存单元定义为一个段。
疑问:段地址可以随便定义吗?
解答:不能。段地址结合偏移地址进行找寻地址。偏移地址有一个范围大小,通过这样的配合可以寻找到一个范围的地址信息,我们找的是范围内的地址,不是一个地址。尽管硬件层面允许,但是逻辑不允许。
检测点 2.2
技巧:
确定位数16位。确定偏移地址的范围为0000H~FFFFH,公式:物理地址 = 段地址 × 16 + 偏移地址。根据公式进行计算。
乘16就是往后加一个零,除以16就是往后面减少一位。
(1) 给定段地址为 0001H,仅通过变化偏移地址寻址,CPU 的寻址范围为____到____。
(2) 有一数据存放在内存 20000H 单元中,现给定段地址为 SA,若想用偏移地址寻到此单元。则 SA 应满足的条件是:最小为______,最大为______。 -------------------------------- 理解 ----------------------------------
提示,反过来思考一下,当段地址给定为多少,CPU 无论怎么变化偏移地址都无法寻到 20000H 单元?<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250910143258975.png" alt="image-20250910143258975" style="zoom: 67%;" />
答案:
00010H 1000FH
1001H 2000H2.9 段寄存器
- 段地址在8086CPU的段寄存器中存放。
- 8086CPU有4个段寄存器:CS、DS、SS、ES。
- 当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
2.10 CS和IP
CS -> code segment
IP -> Instruction Pointer
CS和IP是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。
CS为代码段寄存器,IP为指令指针寄存器。
在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存M×16+N单元开始,读取一条指令并执行。
也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250910150750464.png" alt="image-20250910150750464" style="zoom:67%;" />
执行的详细过程参考P26-31
8086CPU的工作过程可以简要描述如下:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
- IP=IP+所读取指令的长度,从而指向下一条指令;
- 执行指令。转到步骤(1),重复这个过程。
在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。
CPU根据什么将内存中的信息看作指令?
我们可以说,CPU将CS:IP指向的内存单元中的内容看作指令,因为,在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。如果说,内存中的一段信息曾被CPU执行过的话,那么,它所在的内存单元必然被CS:IP指向过。
2.11 修改CS、IP的指令
在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。
8086CPU大部分寄存器的值,都可以用mov指令来改变,mov指令被称为传送指令。
能够改变CS、IP的内容的指令被统称为转移指令。
指令格式:
同时修改CS和IP的值:
assemblyjmp 段地址:偏移地址仅修改IP的内容:(用寄存器中的值修改IP)(寄存器中存储的地址当作IP来使用,可以理解为mov IP,ax)
assejmp ax
问题 2.3
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250910155613297.png" alt="image-20250910155613297" style="zoom: 67%;" />
指令序列:
- mov ax,6622H
- jmp 1000:3
- mov ax,0000
- mov bx,ax
- jmp bx
- mov ax,0123H
- 转到第3步执行
2.12 代码段
- 我们可以将长度为N(N≤64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,我们可以认为,这段内存是用来存放代码的,从而定义了一个代码段。
- CPU只认被CS:IP指向的内存单元中的内容为指令。
- 所以,要让CPU执行我们放在代码段中的指令,必须要将CS:IP指向所定义的代码段中的第一条指令的首地址。
2.9~2.12 小结
- 段地址在8086CPU的段寄存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的段地址。8086CPU有4个段寄存器,其中CS用来存放指令的段地址。
- CS存放指令的段地址,IP存放指令的偏移地址。 8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
- 8086CPU的工作过程:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
- IP指向下一条指令;
- 执行指令。(转到步骤①,重复这个过程。)
- 8086CPU提供转移指令修改CS、IP的内容。
检测点 2.3
- 下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250910161319698.png" alt="image-20250910161319698" style="zoom:67%;" />
答案:
- 共3次修改IP
- 前两次是“指令执行后自动递增IP”,第三次是“jmp”指令主动修改IP
- 最后IP的值为0
实验一
看CPU和内存,用机器指令和汇编指令编程
用Debug的R命令查看、改变CPU寄存器的内容;
用Debug的D命令查看内存中的内容;
用Debug的E命令改写内存中的内容;
用Debug的U命令将内存中的机器指令翻译成汇编指令;
用Debug的T命令执行一条机器指令;
用Debug的A命令以汇编指令的格式在内存中写入一条机器指令。| 类别 | 命令 | 功能 | 记忆点(联想功能) |
|---|---|---|---|
| 寄存器操作 | R | 查看 / 修改寄存器值 | Register(寄存器)的首字母 |
| 内存操作 | D | 显示内存数据( Dump 内存) | Dump(转储)的首字母 |
| 内存操作 | E | 修改内存数据( Edit 内存) | Edit(编辑)的首字母 |
| 反汇编 | U | 将机器码反汇编为汇编指令 | Unassemble(反汇编)的首字母 |
| 汇编 | A | 输入汇编指令( Assemble ) | Assemble(汇编)的首字母 |
| 执行控制 | T | 单步执行( Trace 单步) | Trace(跟踪)的首字母 |
| 执行控制 | G | 连续执行到断点( Go 运行) | Go(运行)的首字母 |
| 断点设置 | B | 设置断点( Breakpoint ) | Breakpoint(断点)的首字母 |
| 程序加载 | L | 从文件加载程序( Load ) | Load(加载)的首字母 |
| 程序保存 | W | 将内存数据保存到文件( Write ) | Write(写入)的首字母 |
| 退出 | Q | 退出 debug( Quit ) | Quit(退出)的首字母 |
R命令
R查看寄存器的内容R 寄存器修改寄存器的值
D命令
D 段地址:偏移地址查看内存中的内容D 段地址:偏移地址 结尾偏移地址查看一定范围内存中的内容
E命令
E 起始地址 数据 数据 数据 ... 数据从指定地址开始依次覆盖数据E 起始地址通过提问方式修改数据。按空格表示下一个,Enter表示确认。支持写入char跟string。支持写入机器码。
U指令
U 地址将内存单元中的机器码翻译为汇编指令
T指令
T执行下一条指令
A指令
A 地址以汇编指令的形式向内容单元中写入机器指令
第三章 寄存器(内存访问)
第二章中,从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关寄存器以及一些指令。
这一章中,我们从访问内存的角度继续学习几个寄存器
3.1 内存中字的存储
- CPU中,用16位寄存器来存储一个字。
- 高8位存放高位字节,低8位存放低位字节。
- 内存单元是字节单元(一个单元存放一个字节),则一个字需要两个地址连续的内存单元来存放。
疑问:内存单元读取数据的顺序如果按照阅读的顺序的话是先从高位读取,再往地位读取。为什么?从0开始编号,依次往后增大,小的是低位字节,大的是高位字节。
- 以后的课程中,我们将起始地址为N的字单元简称为N地址字单元。(N地址为起始地址,由于是字单元(相对于单元占一个),占两个字节)
问题 3.1
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911081247609.png" style="zoom:67%;" />
对于上图:
- 0地址单元中存放的字节型数据是多少?
- 0地址字单元中存放的字型数据是多少?
- 2地址单元中存放的字节型数据是多少?
- 2地址字单元中存放的字型数据是多少?
- 1地址字单元中存放的字型数据是多少?
- 任何两个地址连续的内存单元,N号单元和N+1号单元,可以看成一个地址为N的字单元中的高位字节单元和地位字节单元。
- N+1 --> 高位字节单元 N --> 低位字节单元
答案:
20H
4E20H
12H
0012H
46863.2 DS和[address]
CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。
8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
示例:(将10000H(1000:0)中的数据读到al中)
assemblymov bx,1000H mov ds,bx mov al,[0]说明:
语法:
assmov 寄存器名,内存单元地址寄存器用寄存器名来指明,内存单元用内存单元地址来指明
"[···]"表示一个内存单元,"[···]"中的0表示内存单元的偏移地址。
只有一个偏移地址无法定位一个内存单元,还需要结合段地址。
[]表示操作对象是一个内存单元,0说明这个内存单元的偏移地址是0,段地址默认放在DS寄存器中,自动读取。
DS寄存器是一个段寄存器,不能直接将一个数据(常量)送入段寄存器中,需要一个寄存器进行中转。(硬件设计)
问题 3.2
写几条指令,将al中的数据送入内存单元10000H中,思考后看分析。
分析:
怎样将数据从寄存器送入内存单元?从内存单元到寄存器的格式是:“mov寄存器名,内存单元地址”,从寄存器到内存单元则是:“mov内存单元地址,寄存器名”。10000H可表示为1000:0,用ds存放段地址1000H,偏移地址是0,则mov [0],al可完成从al到10000H的数据传送。完整的几条指令是:
assmov bx,1000H mov ds,bx mov [0],al
3.3 字的传送
8086CPU是16位结构,有16根数据线,一次性传送16位数据,也就是可以一次性传送一个字。
只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。
示例:
assemblymov bx,1000H mov ds,bx mov ax,[0] mov [0],cx
问题 3.3
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911090229205.png" alt="image-20250911090229205" style="zoom:67%;" />
问题 3.4
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911090611468.png" alt="image-20250911090611468" style="zoom:67%;" />
3.4 mov、add、sub指令
前面我们用到了mov、add、sub指令,它们都带有两个操作对象。
到现在,我们知道,mov指令可以有以下几种形式:
assemblymov 寄存器,数据 比如:mov ax,8 mov 寄存器,寄存器 比如:mov ax,bx mov 寄存器,内存单元 比如:mov ax,[0] mov 内存单元,寄存器 比如:mov [0],ax mov 段寄存器,寄存器 比如:mov ds,ax通过debug验证mov的指令语法进行探索:TODO:
mov 寄存器,段寄存器mov 内存单元,段寄存器mov 段寄存器,内存单元- add跟sub同理
3.5 数据段
- 编程时,可以将一组内存单元定义为一个段。当作专门存储数据的内存空间。数据段。Data Segment(DS)
- 将一段内存当作数据段,是我们在编程时的一种安排。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911092111494.png" alt="image-20250911092111494" style="zoom:67%;" />
- al是8位寄存器,使用add指令的时候,只会累加一个内存单元的数据。ax会加两个。
问题 3.5
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911092338469.png" alt="image-20250911092338469" style="zoom:67%;" />
3.1~3.5 总结
- 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
- 用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。
- [address]表示一个偏移地址为address的内存单元。
- 在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
- mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。
- 可以根据自己的推测,在Debug中实验指令的新格式。
检测点 3.1
疑问:ds=1,0000段
在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,写出每条汇编指令执行完后相关寄存器中的值。
assemblymov ax,1 mov ds,ax mov ax,[0000] AX= mov bx,[0001] BX= mov ax,bx AX= mov ax,[0000] AX= mov bx,[0002] BX= add ax,bx AX= add ax,[0004] AX= mov ax,0 AX= mov al,[0002] AX= mov bx,0 BX= mov bl,[000C] BX= add al,bl AX=内存中的情况如图3.6所示。 各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0; ①写出CPU执行的指令序列(用汇编指令写出)。 ②写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。 ③再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911093029726.png" alt="image-20250911093029726" style="zoom:67%;" />
答案:
3.6 栈
栈的访问方式:最后进入这个空间的数据,最先出去。
过程理解参考原书P56
从程序化的角度来讲,应该有一个标记,这个标记一直指示着盒子最上边的数据。
栈有两个基本的操作:入栈和出栈。
- 入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。
- 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
- 栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)
3.7 CPU提供的栈机制
现今的CPU中都有栈的设计。在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈指令。PUSH(入栈)和POP(出栈)。
assemblypush ax # 将寄存器ax中的数据送入栈中 pop ax # 从栈顶取出数据送入ax8086CPU的入栈和出栈操作都是以字(两个字节)为单位进行的。
问:
- CPU如何知道10000H~1000FH这段空间被当作栈来使用?
- push、pop在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?
在8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。
任意时刻,SS:SP指向栈顶元素。
push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911104548239.png" alt="image-20250911104548239" style="zoom: 50%;" />
- 从图中可以看出,入栈时,栈顶从高地址向低地址方向增长。
问题 3.6
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911105959479.png" alt="image-20250911105959479" style="zoom:67%;" />
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250911110031386.png" alt="image-20250911110031386" style="zoom:67%;" />
- pop指令过程类似
3.8 栈顶超界的问题
提出问题:如何保证入栈、出栈时,栈顶不会超出栈空间?
结论:8086CPU的工作机理只考虑当前的情况,只知道当前的栈顶在何处、当前要执行的指令是哪一条。我们需要自己在编程的时候考虑栈顶越界的问题。
问:为什么CPU不考虑这个?
我的疑问:为什么在存储段中设计的栈机制是以存储编号大的为底,而不是编号小的为底?一个一个数据是从0编号开始逐个添加的,这么来看的话,根据惯性思维,应该是小编号为底部,而栈的机制是,将一段连续的内存空间视为栈的内存空间,从编号大的开始作为低添加数据?
3.9 push、pop指令
语法:(注意:栈操作都是以字为单位)
assemblypush 寄存器 pop 寄存器 push 段寄存器 pop 段寄存器 push 内存单元 pop 内存单元
问题 3.7
编程,将10000H~1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈。
答案:
assembly;栈操作之前需要指定栈的地址!!! MOV AX,1000H MOV SS,AX MOV SP,0010H ;因为栈为空,所以初始地址要在栈底的下一个位置 PUSH AX PUSH BX PUSH DS
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250913195233342.png" alt="image-20250913195233342" style="zoom:67%;" />
问题 3.8
编程:
- (1)将10000H~1000FH这段空间当作栈,初始状态栈是空的;
- (2)设置AX=001AH,BX=001BH;
- (3)将AX、BX中的数据入栈;
- (4)然后将AX、BX清零;
- (5)从栈中恢复AX、BX原来的内容。
答案:
assemblyMOV AX,1000H MOV SS,AX MOV SP,0010H MOV AX,001AH MOV BX,001BH PUSH AX PUSH BX ;将数据清零最好用SUB AX,AX ;sub指令的机器码为2个字节 ;mov指令的机器码为3个字节 MOV AX,0 MOV BX,0 POP BX POP AX说明:出栈的顺序和入栈的顺序相反
问题 3.9
编程:
- (1)将10000H~1000FH这段空间当作栈,初始状态栈是空的;
- (2)设置AX=001AH,BX=001BH;
- (3)利用栈,交换AX和BX中的数据。
答案:
assemblyMOV AX,1000H MOV SS,AX MOV SP,0010H MOV AX,001AH MOV BX,001BH PUSH AX PUSH BX POP AX POP BX
问题 3.10
问题:
如果要在10000H处写入字型数据2266H,可以用以下的代码完成:
assemblymov ax,1000H mov ds,ax mov ax,2266H mov [0],ax补全下面的代码,使它能够完成同样的功能:在10000H处写入字型数据2266H。
要求:不能使用“mov 内存单元,寄存器”这类指令。
assembly____________________ ____________________ ____________________ mov ax,2266H push ax答案:
assemblymov ax,1000H mov ss,ax mov sp,2 mov ax,2266H push ax
我的问题:一个两个字节的数据2266H,存在一个地址上,但是一个地址只能存一个字节的数据,是低位数据存在这个指定位置,还是高位数存在这个位置?
栈的总述
(1)8086CPU提供了栈操作机制,方案如下。
在SS、SP中存放栈顶的段地址和偏移地址;
提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。
(2)push指令的执行步骤:①SP=SP-2;②向SS:SP指向的字单元中送入数据。
(3)pop指令的执行步骤:①从SS:SP指向的字单元中读取数据;②SP=SP+2。
(4)任意时刻,SS:SP指向栈顶元素。
(5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。
(6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。
(7)push、pop实质上是一种内存传送指令,注意它们的灵活应用。
栈是一种非常重要的机制,一定要深入理解,灵活掌握。
3.10 栈段
- 将一段内存当作栈段,仅仅是我们在编程时的一种安排,我们需要手动用SS:SP指向我们定义的栈段。
问题 3.11
问题:
- 如果将10000H~1FFFFH这段空间当作栈段,初始状态栈是空的,此时,SS=1000H,SP=?
答案:
- SP=0000H
问题 3.12
- 问题:一个栈段最大可以设为多少?为什么?
- 答案:64KB=2^16B(16进制=16个二进制数)
段的综述
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。
我们可以用一个段存放数据,将它定义为“数据段”;
我们可以用一个段存放代码,将它定义为“代码段”;
我们可以用一个段当作栈,将它定义为“栈段”。
我们可以这样安排,但若要让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、SP,DS的指向。检测点 3.2
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250913202918340.png" alt="image-20250913202918340" style="zoom:67%;" />
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250913202938327.png" alt="image-20250913202938327" style="zoom:67%;" />
答案:
assembly;把目标位置当作栈,指定段地址,改变偏移地址的机制进行复制 mov ax,1000H mov ds,ax mov ax,2000H mov ss,ax mov sp,0010H push [0] push [2] push [4] push [6] push [8] push [A] push [C] push [E] --------------------- ;把目标位置当作段地址,将源数据当作栈,通过栈的机制复制数据 mov ax,2000H mov ds,ax mov ax,1000H mov ss,ax mov sp,0010H pop [E] pop [C] pop [A] pop [8] pop [6] pop [4] pop [2] pop [0]
实验二
用机器指令和汇编指令编程
第四章 第一个程序
4.1 一个源程序从写出到执行的过程
一个汇编语言程序从写出到最终执行的简要过程。具体说明如下:
第一步:编写汇编源程序。
第二步:对源程序进行编译连接。使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。可执行文件包含两部分内容。
● 程序(从源程序中的汇编指令翻译过 来的机器码)和数据(源程序中定义的数据)
● 相关的描述信息(比如,程序有多大、要占用多少内存空间等)
第三步:执行可执行文件中的程序。
4.2 源程序
- 汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
- 伪指令没有对应的机器指令,最终不被CPU所执行。伪指令是由编译器来执行的命令。
段名 segment
...
段名 ends一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。
一个源程序中所有将被计算机所处理的信息:指令、数据、栈,被划分到了不同的段中。
关键字
end:编译结束关键字ends:segment成对使用assume:假设。假设某一段寄存器和程序中的某一个定义的段相关联。
标号
- 一个标号指代了一个地址。这个段的名称最终将被编译、连接程序处理为一个段的段地址。
源程序结构
- 源程序是由一些段构成的。我们可以在这些段中存放代码、数据,或将某个段当作栈空间。
程序返回
固定搭配
assemblymov ax,4c00H int 21H
4.3 编译源程序
使用edit启动DDos的文本编辑器写代码
4.4 编译
- masm指令进行编译
- 编译的时候不强制要求后缀。默认后缀asm可以省略
- 可以指定路径下的文件
- 其他后缀是中间间,不用输出。
4.5 连接
4.6 以简化的方式进行编译和连接
编译时命令行加上分号直接忽略中间件的提示。
4.7 1.exe的执行
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
按照上面的原理,再来看一下4.7节中1.exe的执行过程(思考相关的问题)。
- (1)在提示符“c:\masm”后面输入可执行文件的名字“1”,按Enter键。这时,请思考问题4.1。
- (2)1.exe中的程序运行。
- (3)运行结束,返回,再次显示提示符“c:\masm”。请思考问题4.2。
问题 4.1
此时,有一个正在运行的程序将1.exe中的程序加载入内存,这个正在运行的程序是什么?它将程序加载入内存后,如何使程序得以运行?
问题 4.2
程序运行结束后,返回到哪里?
操作系统的外壳
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统进行工作。DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell。DOS启动时,先完成其他重要的初始化工作,然后运行command.com,command.com运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:\”或“c:\windows”等,然后等待用户的输入。用户可以输入所要执行的命令,比如,cd、dir、type等,这些命令由command执行,command执行完这些命令后,再次显示由当前盘符和当前路径组成的提示符,等待用户的输入。如果用户要执行一个程序,则输入该程序的可执行文件的名称,command首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置CS:IP指向程序的入口。此后,command暂停运行,CPU运行程序。程序运行结束后,返回到command中,command再次显示由当前盘符和当前路径组成的提示符,等待用户的输入。在DOS中,command处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。
回答:
- (1)在DOS中直接执行1.exe时,是正在运行的command,将1.exe中的程序加载入内存;
- (2)command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行;
- (3)程序运行结束后,返回到command中,CPU继续运行command。
汇编程序从写出到执行的过程
到此,完成了一个汇编程序从写出到执行的全部过程。我们经历了这样一个历程:
编程 → 1.asm → 编译 → 1.obj → 连接 → 1.exe → 加载 → 内存中的程序 → 运行
(edit) (masm) (link) (command) (CPU)
4.9 程序执行过程的追踪
通过使用 debug 1.exe 来追踪程序的执行。
- DOS系统中.EXE文件中的程序的加载过程:
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250920193128417.png" alt="image-20250920193128417" style="zoom:67%;" />
注意,有一步称为重定位的工作在上图中没有讲解,因为这个问题和操作系统的关系较大,我们不作讨论。
那么,我们的程序被装入内存的什么地方?我们如何得知?从上图中我们知道以下的信息。 (1)程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为ds:0; (2)这个内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信。从256字节处向后的空间存放的是程序。(段地址+16H)
使用P命令执行 int 21。不必考虑为什么,记住就行了。
- 在DOS中用“debug 1.exe”运行Debug对1.exe进行跟踪时,程序加载的顺序是:
- command加载Debug,Debug加载1.exe。返回的顺序是:从1exe中的程序返回到Debug,从Debug返回到command。
实验三
编程、编译、连接、跟踪
第五章 [BX]和loop指令
- [bx]和内存单元的描述
- 要完整地描述一个内存单元,需要两种信息:①内存单元的地址;②内存单元的长度(类型)。
- [bx]同样也表示一个内存单元,它的偏移地址在bx中,段地址在ds中
- loop
- 和循环有关
- 我们定义的描述性的符号:"()"
- 我们将使用一个描述性的符号“()”来表示一个寄存器或一个内存单元中的内容。
- 注意,“()”中的元素可以有3种类型:①寄存器名;②段寄存器名;③内存单元的物理地址(一个20位数据)。
- (ax)、(ds)、(al)、(cx)、(20000H)、((ds)*16+(bx))等是正确的用法;(2000:0)、((ds):1000H)等是不正确的用法。
- “(X)”所表示的数据有两种类型:①字节;②字。是哪种类型由寄存器名或具体的运算决定。
- 约定符号 idata 表示常量
- 在“[….]”里用一个常量0表示内存单元的偏移地址。以后,我们用idata表示常量。
- mov ax,[idata]就代表mov ax,[1]、mov ax,[2]、mov ax,[3]等。
- mov bx,idata就代表mov bx,1、mov bx,2、mov bx,3等。
- mov ds,idata就代表mov ds,1、mov ds,2等,它们都是非法指令。
5.1 [BX]
- mov ax,[bx]
- 功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中。即:(ax)=((ds)*16+(bx))。
- mov [bx],ax
- 功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处。即:((ds)*16+(bx))=(ax)。
问题 5.1

注意,inc bx的含义是bx中的内容加1,比如下面两条指令:
mov bx,1 inc bx执行后,bx=2。
答案:
<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250920201730860.png" alt="image-20250920201730860" style="zoom:67%;" />
5.2 loop指令
格式:
loop 标号CPU执行loop指令的时候,要进行两步操作,
- ①(cx)=(cx)-1;
- ②判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
cx中的值影响着loop指令的执行结果。通常(注意,我们说的是通常)我们用loop指令来实现循环功能,cx中存放循环次数。
loop程序示例:(计算 $2^{12}$)
assemblyassume cS:code code segment mov ax,2 mov cx,11 S: add ax,ax loop s mov ax,4c00h int 21h code ends end从上面的过程中,我们可以总结出用cx和loop指令相配合实现循环功能的3个要点:
- (1)在cx中存放循环次数;
- (2)loop指令中的标号所标识地址要在前面;
- (3)要循环执行的程序段,要写在标号和loop指令的中间。
用cx和loop指令相配合实现循环功能的程序框架如下。
assemblymov cx,循环次数 S: 循环执行的程序段 loop s
问题 5.2
问题:编程,用加法计算123*236,结果存在ax中。
分析:可用循环完成,将123加236次。可先设(ax)=0,然后循环做236次(ax)=(ax)+123。
解答:
assemblyassume cS:code code segment mov ax,0 mov cx,236 s: add ax,123 loop s mov ax,4c00hint 21h code ends end
问题 5.3
- 问题:改进程序5.2,提高123*236的计算速度。
- 我们可以将236加123次。可先设(ax)=0,然后循环做123次(ax)=(ax)+236,这样可以用123次加法实现相同功能。
5.3 在Debug中跟踪用loop指令实现的循环程序
在汇编程序中,数据不能以字母开头。A000h要写成0A000h。前面补零。
指令
G 偏移地址:将指令从CS:IP处一直执行到偏移地址为止指令
P:自动重复执行循环中的指令,直到(cx)=0为止
5.4 Debug和汇编编译器masm对指令的不同处理
在Debug中,编译器将 “[idata]” 解释为一个内存单元
在源程序中,编译器将 “[idata]” 解释为 "idata"
源程序的处理方式:
mov al,ds:[bx]显示地给出段地址,解释为一个内存单元,而不是一个常数
5.5 loop和[bx]的联合应用
进行运算前,先考虑是否会超出所能存储的最大范围
assume cS:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0 ;初始化ds:bx指向ffff:0
mov dx,0 ;初始化累加寄存器dx,(dx)=0
mov Cx,12 ;初始化循环计数寄存器cx,(cx)=12
S: mov al,[bx]
mov ah,0
add dx,ax ;间接向dx中加上((ds)*16+(bx))单元的数值
inc bx ;ds:bx指向下一个单元
loop s
mov ax,4c00h
int 21h
code ends
end5.6 段前缀
这些出现在访问内存单元的指令中,用于显示地指明内存单元的段地址的“ds:"、"cs:"、"ss:"、"es:",在汇编语言中称为段前缀
- assembly
mov ax,ds:[dx] mov ax,cs:[dx] mov ax,ss:[dx] mov ax,es:[dx]
5.7 一段安全的空间
在8086模式中,随意向一段内存空间写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码。比如下面的指令:
assemblymov ax,1000h mov ds,ax mov al,0 mov ds:[0],almov ds:[0],al可能会将系统数据覆盖掉,造成系统程序严重的问题。可见,在不能确定一段内存空间中是否存放着重要的数据或代码的时候,不能随意向其中写入内容。
要使用操作系统给我们分配的空间,而不应直接使用地址任意指定内存单元。
在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用0:200~0:2ff(00200h~002ffh)的256个字节的空间。所以,我们使用这段空间是安全的。不过为了谨慎起见,在进入DOS后,我们可以先用Debug查看一下,如果0:200~0:2ff单元的内容都是0的话,则证明DOS和其他合法的程序没有使用这里。
总结:
- (1)我们需要直接向一段内存中写入内容;
- (2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误;
- (3)DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码;
- (4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2ff这段空间。
5.8 段前缀的使用
问题:我们考虑一个问题,将内存ffff:0~ffff:b单元中的数据复制到0:200~0:20b单元中。
assemblyassume cs:code code segment mov ax,Offffh mov ds,ax ;(ds)=0ffffh mov ax,0020h mov es,ax ;(es)=0020h mov bx,0 ;(bx)=0,此时ds:bx指向ffff:0,es:bx指向0020:0 mov CX,12 ;(cx)=12,循环12次 s: mov dl,[bx] ;(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl mov es:[bx],dl ;((es)*16+(bx))=(dl),将dl中的数据送入0020:bx inc bx ;(bx)=(bx)+1 loop s mov ax,4c00h int 21h code ends end使用es存放目标空间0020:0~0020:b的段地址,用ds存放源始空间fff:0~ffff:b的段地址。在访问内存单元的指令“mov es:[bx],al”中,显式地用段前缀“es:”给出单元的段地址,这样就不必在循环中重复设置ds。
实验四
[bx]和loop的使用
(1)编程,向内存0:200~0:23F依次传送数据0~63(3FH)。
(2)编程,向内存0:200~0:23F依次传送数据0~63(3FH),程序中只能使用9条指令,9条指令中包括“mov ax,4c00h”和“int 21h”。
(3)下面的程序的功能是将“mov ax,4c00h”之前的指令复制到内存0:200处,补全程序。上机调试,跟踪运行结果。
assume cs:code
code segment
mov ax,____
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov CX,____
s:mov al,[bx]
mov es:[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end提示:
(1)复制的是什么?从哪里到哪里?
(2)复制的是什么?有多少个字节?你如何知道要复制的字节的数量?
第六章 包含多个段的程序
- 程序取得所需空间的方法有两种,
- 一是在加载程序的时候为程序分配,
- 再就是程序在执行的过程中向系统申请。在我们的课程中,不讨论第二种方法。
(1)在一个段中存放数据、代码、栈,我们先来体会一下不使用多个段时的情况;
(2)将数据、代码、栈放入不同的段中。