前言:本笔记主要记录自己在学习过程中的易错、易混淆点,单看笔记可能无法串联知识点,需要结合书本才行。
机器指令:CPU能直接识别并执行的二进制数字。
汇编指令:机器指令的助记符,同机器指令一一对应。
指令:指令通常由操作码和地址码(操作数)两部分组成
指令集:每种CPU都有自己的汇编指令集。
机器语言:机器指令的集合。
汇编语言由3类指令组成。
编译器:能将汇编指令转换成机器指令的翻译程序。
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
随机存储器(RAM)在程序的执行过程中可读可写,但必须带电存储,关机后存储的内容丢失。
只读存储器(ROM)在程序的执行过程中只读,关机后数据不丢失。
CPU要想进行数据的读写,必须和外部器件(芯片)进行以下三类信息的交互:
那CPU是通过什么将地址、数据和控制信息传到存储器芯片中的呢?
答:在计算机中专门有连接CPU和其他芯片的导线,通常称为总线 (物理上来讲,就是一根根导线的集合)
地址总线:CPU通过地址总线来指定存储单元的地址
一个CPU有N根地址线,则可以说这个CPU的地址总线宽度为N。这样的CPU最多可以寻找 2^n 个内存单元。
数据总线:数据总线的宽度决定了CPU和外界的数据传送速度。
8根数据总线一次可以传送一个8位二进制数据(即一个字节)。
控制总线:有多少根控制总线,就意味着CPU对外部器件提供多少种控制。
CPU对外设都不能直接控制,如显示器、音箱、打印机等。
直接控制这些设备进行工作的是插在扩展插槽上的接口卡。CPU可以直接控制这些接口卡,从而实现CPU对外设的间接控制。
如:CPU无法直接控制显示器,但CPU可以直接控制显卡,从而实现对显示器的间接控制。
CPU将系统中各类存储器看作一个逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。
CPU将各类存储器看作一个逻辑存储器:
我们在基于一个计算机硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况。因为当我们在某类存储器中读写数据的时候,必须知道它的第一个单元的地址和最后一个单元的地址,才能保证读写操作是在预期的存储器中进行。
CPU由运算器、控制器、寄存器等器件构成,这些器件靠内部总线相连。
运算器进行信息处理;控制器控制各种器件进行工作;寄存器进行信息存储;内部总线连接各种器件,在它们之间进行数据的传送。
8086CPU为了兼容上一代的8位寄存器,AX,BX,CX,DX这四个寄存器可以拆开成两个 独立的 8位的寄存器来使用。分别为AH,AL, BH,BL, CH,CL, DH,DL。低八位(编号0-7)构成L寄存器,高八位构成H寄存器。
汇编指令或寄存器名称不区分大小写。
在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
地址加法器采用 物理地址 = 段地址×16 + 偏移地址 的方法用段地址和偏移地址合成物理地址。
“段地址x16” 其实就是二进制数左移4位。
拓展:一个数据的十六进制形式左移1位,相当于乘以16;一个数据的十进制形式左移1位,相当于乘以10;一个X进制的数据左移1位,相当于乘以X。
CS为代码段寄存器,IP为指令指针寄存器,它们的内容提供了CPU要执行指令的地址。
8086CPU的工作过程简要描述:
mov指令(传送指令)不能用于设置CS、IP的值,8086CPU提供转移指令修改CS、IP的内容。
jmp 2AE3:3
jmp ax
,在含义上好似:mov IP,ax
字单元:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。
对于字,只要在mov指令中给出16位的寄存器(如,用AX而不是AL/AH)就可以进行16位数据的传送。
DS寄存器:存放要访问数据的段地址
[address]:表示一个偏移地址为address的内存单元(8086CPU自动取ds中的数据为内存单元的段地址)。
通过段地址和偏移地址即可定位内存单元。
8086CPU提供入栈和出栈指令,即可以将一段内存当作栈来使用。
8086CPU的入栈和出栈操作都是以字为单位进行的!
问:CPU如何知道栈顶的位置?
答:8086CPU中由两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素
入栈
push ax
表示将寄存器ax中的数据送入栈中,由两步完成。
入栈时,栈顶从高地址向低地址方向增长。
出栈
pop ax
表示从栈顶取出数据送入ax,由以下两步完成。
8086CPU中没duan有记录栈顶上限和栈底的寄存器,因此需要程序员自己操心栈顶超界的问题。
CPU将内存中的某段内容当作 代码,是因 CS:IP
指向了那里;CPU将内存中的某段内容当作 栈,是因为 SS:SP
指向了那里。一定要弄清楚什么是我们的安排以及如何让CPU按照我们的安排行事。
先看一段简单的汇编语言源程序
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00
int 21H
codesg ends
end
对程序进行说明:
segment ···ends
定义的段关联。mov ax, 4c00 int 21H
DOS系统中 .EXE文件中的程序的加载过程
[bx] :同样表示一个内存单元,它的偏移地址在bx中,段地址默认在ds中。
loop(循环)指令:loop 标号,CPU执行loop指令的时候,要进行两步操作:
程序示例:编程计算2^12。
assume cs:code
code segment
mov ax, 2
mov cx, 11
s: add ax, ax ;汇编中,这的标号s 实际标识了一个地址,这个地址处有一条指令:add ax,ax。
loop s
mov ax,4c00h
int 21h
code ends
end
用 cx 和 loop 指令相配合实现循环功能的程序框架如下:
mov cx,循环次数
s:
循环执行的程序段
loop s
形如 mov ax,[idata]
的指令在Debug和masm中有着不同的解释:
Debug是将它解释为 “[idata]” 是一个内存单元,“idata” 是内存单元的偏移地址;
而编译器masm中则是将 “[idata]” 解释为“idata”。
解决方法1:先将偏移地址送入BX,然后再使用mov ax,[bx]
解决方法2:直接在 “[ ]” 的前面显式地给出段地址所在的段寄存器,如 mov al, ds:[0]
。
这种写法通过编译器之后会变成Debug中的 mov al,[0]
拓展:
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”,“cs:”,“ss:”,“es:”,在汇编语言中称为段前缀。
直接看例子:
计算ffff:0 ~ ffff:b
单元中的数据的和,结果存储在 dx 中
分析:
解决方案:
assume cs:code
code segment
mov ax, 0ffffh ;在汇编源程序中,数据不能以字母开头,所以要在前面加0
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
end
之前没有提到的一个问题,如果在写程序之前不关注要操作的内存,直接开始使用,如果改写了内存中重要的系统数据,可能会引起系统崩溃。可见,在不能确定一段内存空间中是否存放着重要数据或代码的时候,不能随意向其中写入内容。
一般操作系统和合法程序都不会使用0:200~0:2ff
这256个字节的空间,所以可以使用这段安全的空间。
学习汇编语言,要通过它来获得底层的编程体验,理解计算机底层的基本工作机理。所以我们尽量直接对硬件编程,而不理会操作系统。
这在纯DOS方式(实模式)下是可以做到的,但在windows或Unix这种运行与CPU保护模式的操作系统上是不可能的,因为这种操作系统已经将CPU全面严格的管理了。
一个问题:根据什么设置CPU的 CS:IP 指向程序的第一条要执行的指令?
这一点,是由可执行文件中的描述信息指明的。在一个程序文件中,用伪指令end描述程序的结束和程序的入口。
在编译,连接后,由“end start”指明的程序入口被转化为一个入口地址,存储在可执行文件的描述信息中。
当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP。这样CPU就从我们希望的地址处开始执行。
在代码段中使用数据
示例:利用栈,将程序中定义的数据逆序存放
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 0:0~0:15单元
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 0:16~0:47单元的空间作为栈使用
start: mov ax, cs
mov ss, ax
mov sp, 30h ;将设置栈顶ss:sp指向栈底cs:30
mov bx, 0
mov cx, 8
s: push cs:[bx]
add bx, 2
loop s ;以上将代码段0~15单元中的8个字型数据依次入栈
mov bx, 0
mov cx, 8
s0: pop cs:[bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到代码段0~15单元中
mov ax,4c00h
int 21h
codesg ends
end start ;指明程序的入口在start处
在描述dw的作用时,不仅可以说用它来定义数据,也可以说用它来开辟内存空间留给之后的程序使用。
将数据、代码、栈放入不同的段
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;0-15单元
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;0-31单元
stack ends
code segment
start: mov ax, stack ;将名称为“stack”的段的段地址送入ax
mov ss, ax
mov sp, 20h ;设置栈顶ss:sp指向stack:20
mov ax, data ;将名称为“data”的段的段地址送入ax
mov ds, ax ;ds指向data段
mov bx, 0 ;ds:bx指向data段中的第一个单元
mov cx, 8
s: push [bx]
add bx, 2
loop s ;以上将data段中的0~15单元中的8个字型数据依次入栈
mov bx, 0
mov cx, 8
s0: pop [bx]
add bx, 2
loop s0 ;以上依次出栈8个字型数据到data段的0~15单元中
mov ax, 4c00h
int 21h
code ends
end start
总之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是考程序中具体的汇编指令,和汇编指令对 CS:IP、SS:IP、DS 等寄存器 的设置来决定的。
and指令:逻辑与指令,按位进行与运算。
例如指令:
mov al, 01100011B
and al, 00111011B
执行后:al=00100011B
or指令:逻辑或指令,按位进行或运算。
例如指令:
mov al, 01100011B
or al, 00111011B
执行后:al=01111011B
世界上有很多编码方案,有一种方案叫做ASCII编码,是在计算机系统中通常被采用的。简单地说,所谓编码方案,就是一套规则,它约定了用什么样的信息来表示现实对象。比如说,在ASCII编码方案中,用 61H 表示 “a”, 62H 表示 “b”。一种规则需要人们遵守才有意义。
在文本编辑过程中,我们按一下键盘的a键,就会在屏幕上看到“a”。这是怎样一个过程呢?
我们按下键盘的a键,这个按键的信息被送入计算机,计算机用ASCII码的规则对其进行编码,将其转化为 61H 存储在内存的指定空间中; 文本编辑软件从内存中取出 61H ,将其送到显卡上的显存中; 工作在文本模式下的显卡,用ASCII码的规则解释显存中的内容,61H被当作字符“a”,显卡驱动显示器,将字符“a”的图像画在屏幕上。
在汇编语言中,用 '···'
的方式指明数据是以字符形式给出的,编译器会自动将它们转化为ASCII码。
小写字母的ASCII码值比大写字母的ASCII码值大20H。
重新观察:就 ASCII 码的二进制形式来看,除第五位(位数从0开始计算)外,大写字母和小写字母的其他各位都一样。大写字母 ASCII 码的第5位为0,小写字母的第5位为1。
程序示例:将datasg中的第一个字符串转化为大写,第二个字符串转化为小写
assume cs:codesg, ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTion'
datasg end
codesg segment
start: mov ax, datasg
mov ds, ax ;设置ds 指向 datasg段
mov bx, 0 ;设置(bx)=0,ds:bx指向'BaSic'的第一个字母
mov cx, 5 ;设置循环次数5,因为'Basic'有5个字母
s: mov al, [bx] ;将ASCII码从ds:bx所指向的单元中取出
and al, 11011111B ;将al中的ASCII码的第5位置为0,变为大写字母
mov [bx], al ;将转变后的ASCII码写回原单元
inc bx ;(bx)加1,ds:bx指向下一个字母
loop s
mov bx, 5 ;设置(bx)=5,ds:bx指向,'iNfOrMaTion'的第一个字母
mov cx, 11 ;设置循环次数11,因为'iNfOrMaTion'有11个字母
s0: mov al, [bx]
or al, 00100000B ;将a1中的ASCII码的第5位置为1,变为小写字母
mov [bx], al
inc bx
loop s0
mov ax, 4c00h
int 21h
codesg ends
除了前面使用 [bx] 来指明一个内存单元外,还可以使用一种更灵活的方式来指明内存单元: [bx+idata] 表示一个内存单元,它的偏移地址为 (bx)+idata(bx中的数值加idata)的内存单元。
数字化的描述为:(ax)=((ds)*16+(bx)+idata)
也可写为[idata+bx],还可写为:idata[bx],[bx].idata
si 和 di 功能和BX相似,但si 和 di 不可以分为两个8位寄存器。
[bx+si]和[bx+di]的含义相似:
[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)
指令mov ax, [bx + si]
的含义:将一个内存单元的内容(长度为2字节)送入ax,存放一个字,偏移地址为 bx 中的数值加上 si 中的数值,段地址在 ds 中。
该指令也可以写成如下格式:mov ax, [bx][si]
[bx+si+idata]和[bx+di+idata]的含义相似:
[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata
··· ···
总结一下上面几种定位内存地址的方法:
程序示例:将 datasg 段中每个单词的头一个字母改为大写字母
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm ' ;16个字节
db 'dec '
db 'dos '
db 'vax '
datasg ends
stacksg segment ;定义一个段,用来做栈段,容量为16个字节
dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends
codesg segment
start: mov ax, stacksg
mov ss, ax
mov sp, 16
mov ax, datasg
mov ds, ax
mov bx, 0
;cx为默认循环计数器,二重循环只有一个计数器,所以外层循环先保存cx值,再恢复,采用栈保存
mov cx, 4
s0: push cx ;将外层循环的cx值入栈
mov si, 0
mov cx, 3 ;cx设置为内层循环的次数
s: mov al, [bx+si]
and al, 11011111b ;每个字符转为大写字母
mov [bx+si], al
inc si
loop s
add bx, 16
pop cx ;从栈顶弹出原cx的值,恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1
mov ax,4c00H
int 21H
codesg ends
end start
一般来说,在需要暂存数据的时候,我们都应该使用栈。
在8086CPU中,只有这4个寄存器可以用在“[…]”中来进行内存单元的寻址。
在[ ]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。(看清楚关系)
只要在[…]中使用寄存器bp,而指令中没有显性地给出段地址, 段地址就默认在ss中。
绝大多数机器指令都是进行数据处理的指令,处理大致可分为3类:读取、写入、运算。
在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口。
示例:指令举例
汇编语言中用3个概念来表达数据的位置
(1) 立即数(idata)
mov ax, 1
add bx, 2000h
or bx, 00010000b
mov al, 'a'
(2) 寄存器
mov ax, bx
mov ds, ax
push bx
mov ds:[0], bx
push ds
mov ss, ax
mov sp, ax
(3) 段地址(SA)和偏移地址(EA)
mov ax, [0]
mov ax, [di]
mov ax, [bx+8]
mov ax, [bx+si]
mov ax, [bx+si+8]
mov ax, [bp]
mov ax, [bp+8]
mov ax, [bp+si]
mov ax, [bp+si+8]
mov ax, ds:[bp]
mov ax, es:[bx]
mov ax, ss:[bx+si]
mov ax, cs:[bx+si+8]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzHjb1Bj-1637678343673)(H:\typora_Note\picture\image-20210625122127619.png)]
8086CPU的指令,可以处理两种尺寸的数据,byte 和 word。所以在机器指令中要指明指令进行的是字操作还是字节操作。
使用div做除法的时候应注意以下问题:
格式:
div reg
div 内存单元
db 和 dw 定义字节型数据和字型数据。
dd 是用来定义 dword(double word,双字) 型数据的伪指令。
dup 是一个操作符,在汇编语言中同 db、dw、dd 等一样,也是由编译器识别处理的符号。
dup 和 db、dw、dd 等数据定义伪指令配合使用,用来进行数据的重复。
示例:
db 3 dup (0) ;定义了3个字节,它们的值都是0,相当于db 0,0,0
db 3 dup (0, 1, 2) ;定义了9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2
db 3 dup ('abc', 'ABC') ;定义了18个字节,它们是'abcABCabcABCabcABCC',相当于db'abcABCabcABCabcABC'
可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。
8086CPU的转移行为有以下几类。
由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。
8086CPU的转移指令分为以下几类。
操作符offset 在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。
示例:
assume cs:codesg
codesg segment
start:mov ax,offset start ;相当于 mov ax,0
s:mov ax,offset s ;相当于 mov ax,3
codesg ends
ends
jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP;
jmp指令要给出两种信息:
jmp short 标号
(转到标号处执行指令)(段内短转移)
示例:
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax, 1 ;被越过
s:inc ax ;程序执行后, ax中的值为 1
codesg ends
end start
jmp short s
指令的读取和执行过程:
在“jmp short 标号
” 指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的 “标号” 计算出来的。
“jmp short 标号
”的功能为 (IP)=(IP)+8
位位移:
还有一种和 “jmp short 标号
” 功能相近的指令格式,jmp near ptr 标号
,它实现的是段内近转移。
指令“jmp near ptr 标号
”的功能为:(IP) = (IP) + 16位位移
。
jmp far ptr 标号
(段间转移,又称远转移)
指令 “jmp far ptr 标号
” 功能如下:
assume cs:codesg
codesg segment
start: mov ax, 0
mov bx, 0
jmp far ptr s
db 256 dup (0)
s:add ax,1
inc ax
codesg ends
end start
格式:jmp 16位 reg
功能:IP =(16位 reg)
转移地址在内存中的jmp指令有两种格式:
(1)jmp word ptr 内存单元地址
(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
示例:
mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]
;执行后,(IP)=0123H
mov ax,0123H
mov [bx],ax
jmp word ptr [bx]
;执行后,(IP)=0123H
(2)jmp dword ptr 内存单元地址
(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。
示例:
mov ax, 0123H
mov ds:[0], ax
mov word ptr ds:[2], 0
jmp dword ptr ds:[0]
;执行后,(CS)=0,(IP)=0123H,CS:IP 指向 0000:0123
jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移。
在对应的机器码中包含转移的位移,而不是目的地址。
对IP的修改范围都为-128~127。
指令格式:jcxz 标号
(如果(cx)=0,转移到标号处执行。)
操作:
当(cx) = 0时,(IP) = (IP) + 8位位移;
当(cx) ≠ 0时,什么也不做(程序向下执行)
从jcxz的功能中可以看出,“jcxz 标号
” 的功能相当于:if((cx)==0) jmp short 标号
loop指令为循环指令,所有的循环指令都是短转移。
在对应的机器码中包含转移的位移,而不是目的地址。
对IP的修改范围都为:-128~127。
指令格式:loop 标号
((cx) = (cx) - 1,如果(cx) ≠ 0,转移到标号处执行)。
操作:
如果(cx)= 0,什么也不做(程序向下执行)。
从loop 的功能中可以看出,“loop 标号
” 的功能相当于:(cx)--; if((cx) != 0) jmp short 标号
。
根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将会报错。
call 和 ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。(经常被共同用来实现子程序的设计)
CPU执行ret指令时,相当于进行:pop IP
,具体进行下面两步操作:
CPU执行retf指令时,相当于进行:pop IP, pop CS
:具体进行下面四步操作:
示例:
assume cs:code
stack seqment
db 16 dup (0)
stack ends
code segment
mov ax, 4c00h
int 21h
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push ax
mov bx, 0
ret
code ends
end start
;ret指令执行后,(IP)=0,CS:IP指向代码段的第一条指令。
CPU执行call指令时,进行两步操作:
call指令 不能实现短转移 ,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。
call 标号
(将当前的IP压栈后,转到标号处执行指令)
操作:
CPU执行此种格式的call指令时,相当于进行 push IP
jmp near ptr 标号
call far ptr 标号
(段间转移)
操作:
CPU执行此种格式的call指令时,相当于进行:push CS
push IP
jmp far ptr 标号
call 16位reg
功能:
CPU执行此种格式的call指令时,相当于进行: push IP
jmp 16位reg
有两种格式:
(1)call word ptr 内存单元地址
CPU执行此种格式的call指令时,相当于进行:push IP
jmp word ptr 内存单元地址
(2)call dword ptr 内存单元地址
CPU执行此种格式的call指令时,相当于进行:push CS
push IP
jmp dword ptr 内存单元地址
配合使用call 和 ret 实现子程序的框架:
assume cs:code
code segment
main: :
:
call sub1 ;调用子程序sub1
:
mov ax,4c00h
int 21h
sub1: : ;子程序sub1开始
:
call sub2 ;调用子程序sub2
:
ret ;子程序返回
sub2: : ;子程序sub2开始
:
ret ;子程序返回
code ends
end main
mul是乘法指令,使用 mul 做乘法时,应注意:
(1)相乘的两个数,要么都是8位,要么都是16位。
AL
中,另一个放在 8位寄存器
或内存字节单元
中;AX
中,另一个放在 16 位寄存器
或内存字单元
中。(2)结果:
AX
中;DX
中,低位在 AX
中。格式:mul reg
, mul 内存单元
示例:
1.计算100*10
;100和10小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl
;结果: (ax)=1000(03E8H)
2.计算100*10000
;100小于255,可10000大于255,所以必须做16位乘法
mov ax,100
mov bx,10000
mul bx
;结果: (ax)=4240H,(dx)=000FH (F4240H=1000000)
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为CPU执行相关指令提供行为依据;
(3)用来控制CPU的相关工作方式。
这种特殊的寄存器在8086CPU中,被称为标志寄存器(flag),其中存储的信息通常被称为程序状态字(PSW)。
flag寄存器不同于其他寄存器,它是按位起作用的(其余的寄存器是用来存放数据的),每一位都有专门的含义,记录特定的信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1PkENcc-1637678343682)(H:\typora_Note\picture\image-20210629145021777.png)]
注意:
在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令。
在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响。
flag 的第6位,它记录相关指令执行后,其结果是否为0。
mov ax, 2
sub ax, 1
;执行后,结果不为0,则zf = 0
flag 的第2位,它记录相关指令执行后,其结果的所有 bit 位中1的个数是否为偶数。
mov al, 1
or al, 2
;执行后,结果为00000011B,其中有2(偶数)个1,则pf = 1;
flag 的第7位,它记录相关指令执行后,其结果是否为负。
计算机中通常用补码表示数据,一个数可以看成有符号数或无符号数。
如:
00000001B,可以看成无符号1,或有符号+1
10000001B,可以看成无符号129,或有符号-127
CPU在执行add等指令的时候,就包含了两种含义:
关键在于我们的程序需要用到哪一种结果。
总结:SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。
CF标志(进位标志位)
flag 的第0位,一般情况下,在进行 无符号数运算 的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0efwO2R-1637678343684)(H:\typora_Note\picture\image-20210629152454655.png)]
例如:
两个8位数据:98H + 98H,将产生进位。
两个8位数据:97H - 98H,将产生借位。
8086CPU中flag的CF位就是用来记录这个 进位/借位值的。
OF寄存器(溢出标志位)
什么是溢出呢?
指令运算的结果用8位寄存器或一个内存单元存放,那么对于8位的有符号数据,机器能表示的范围就是-128~127,16位同理。在进行有符号运算的时候,如果结果超过了机器能表示的范围称为“溢出”。
flag 的第11位,一般情况下,OF记录了 有符号数运算 的结果是否发生了溢出。
注意!!
CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。
CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。
示例:
mov al, 98
add al, 99
;add执行后:CF=0,OF=1。
mov al,0F0H
add al,88H
;add执行后:CF=1,OF=1。
mov al,0F0H
add al,088H
;add执行后:CF=1,OF=0。
可以看出,CF和OF所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,他们之间没有任何关系。
adc指令
adc是带进位加法指令,它利用了CF位上记录的进位值。
指令格式:adc 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
例如:
mov ax, 2
mov bx, 1
sub bx, ax ;无符号运算借位CF=1,有符号运算OF = 0
adc ax, 1
;执行后,(ax)= 4。adc执行时,相当于计算:(ax)+1+CF = 2+1+1 = 4。
加法可以分为两步来进行:(1)低位相加;(2)高位相加再加上低位相加产生的进位值。
注:inc和loop指令不影响CF位。
sbb指令
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
例如:计算 003E1000H - 00202000H,结果放在ax,bx中
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。
cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令格式:cmp 操作对象1,操作对象2
功能:计算对操作对象1 - 操作对象2但不保存结果,仅仅根据结果对标志位进行设置。
例如:
指令cmp ax, ax
,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。
其实,可以通过执行cmp ax,bx
,相关标志位的值就可以判断结果:
若(ax)=(bx) 则(ax)-(bx)=0,所以:zf=1
若(ax)!=(bx) 则(ax)-(bx)!=0,所以:zf=0
若(ax)<(bx) 则(ax)-(bx)产生借位,所以:cf=1
若(ax)>=(bx) 则(ax)-(bx)不产生借位,所以:cf=0
若(ax)>(bx) 则(ax)-(bx)既不产生借位,结果又不为0,所以:cf=0且zf=0
若(ax)<=(bx) 则(ax)-(bx)既可能借位,结果可能为0,所以:cf=1或zf=1
实际上往往会出现溢出,所以,我们应该在考察sf(得知实际结果的正负)的同时考查of(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
下面,以cmp ah,bh
为例,总结一下CPU执行cmp指令后,sf 和 of 的值是如何来说明比较的结果的。
“转移” 指的是它能够修改IP,而 “条件” 指的是它可以根据某种条件,决定是否修改IP。
如:jcxz 就是一个条件转移指令,它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。
所有条件转移指令的转移位移都是[-128,127]。
大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP。
这些条件转移指令通常都和cmp相配合使用,检测不同的标志位来达到不同的条件跳转效果:
下面是常用的根据无符号数的比较结果进行转移的条件转移指令。
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf = 1 |
jne | 不等于则转移 | zf = 0 |
jb | 低于则转移 | cf = 1 |
jnb | 不低于则转移 | cf = 0 |
ja | 高于则转移 | cf = 0 且 zf = 0 |
jna | 不高于则转移 | cf = 1 且 zf = 1 |
j:jump,e:equal,ne:not equal,b:below,nb:not below,a:above,na:not above。
例:统计data中数值小于8的字节个数,用ax保存
···
data segment
db 8,11,8,1,8,5,63,38
data ends
···
mov ax,data
mov ds,ax
mov ax,0 ;初始化累加器
mov bx,0 ;ds:bx指向第一个字节
mov cx,8
s: cmp byte ptr [bx],8 ;和8进行比较
jne next ;如果不小于8 转到next,继续循环
inc ax ;如果小于8 就将计数值加1
next:inc bx
loop s
···
; 程序执行后:(ax)=2
flag 的第10位是DF。在串处理指令中,控制每次操作后si、di的增减。
格式:movsb
功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减。
格式:movsw
功能:将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用:
格式:rep movsb
功能:rep的作用是根据cx的值,重复执行后面的串传送指令。
8086CPU提供下面两条指令对df位进行设置。
示例:用串传送指令,将data段中第一个字符串复制到他后面的空间中
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
mov ax,data
mov ds,ax
mov si,0 ;ds:si 指向 data:0
mov es,ax
mov di,16 ;es:di 指向 data:0010
mov cx,16 ;(cx)=16,rep循环16次
cld ;设置df=0,正向传送
rep movsb
···
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中
pushf和popf,为直接访问标志寄存器提供了一种方法。
任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
中断信息可以来自CPU的内部和外部(内中断,外中断)
内中断:当CPU的内部有以下需要处理的事情发生时,将产生中断信息,引发中断过程。
(1)除法错误,比如,执行div指令产生的除法溢出;
(2)单步执行;
(3)执行 into指令;
(4)执行 int指令。
CPU首先要知道,所接收到的中断信息的来源。8086CPU用称为中断类型码 的数据来标识中断信息的来源。
中断类型码为一个字节型数据,可以表示256种中断信息的来源(中断源)。
上述的4种中断源,在8086CPU中的中断类型码如下:
(1)除法错误:0
(2)单步执行:1
(3)执行into指令:4
(4)执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
中断处理程序
我们编写的,用来处理中断信息的程序被称为中断处理程序。
根据CPU的设计,中断类型码的作用就是用来定位中断处理程序的。那如何根据8位的中断类型码得到中断处理程序的段地址和偏移地址呢?
中断向量表
CPU用8位的中断类型码通过 中断向量表找到相应的中断处理程序入口地址。(中断向量表就是中断向量的列表,中断向量就是中断处理程序的入口地址)
CPU会自动根据中断类型找到对应的中断向量并设置CS和IP的值,CPU硬件完成这个工作的过程称为中断过程。
具体步骤:
CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断处理程序常规的步骤:
(1)保存用到的寄存器;
(2)处理中断;
(3)恢复用到的寄存器;
(4)用iret指令返回。
iret通常和硬件自动完成的中断过程配合使用。
iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。
当CPU执行div 等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行0号中断对应的中断处理程序。
mov ax, 1000h
mov bh, 1
div bh
;发生除法溢出错误
实例:编程,当发生除法溢出时,在屏幕中间显示“overflow!”,返回DOS。
分析:
程序框架:
assume cs:code
code segment
start: do0安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串"overflow!"
mov ax,4c00h
int 21th
code ends
end start
完整程序:
assume cs:code
code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0 ;设置ds:si指向源地址
mov ax, 0
mov es, ax
mov di, 200h ;设置es:di指向目的地址0000:0200
mov cx, offset do0end - offset do0 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
;设置中断向量表
mov ax, 0
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4+2], 0
mov ax,4c00h
int 21h
;do0程序的主要任务是显示字符串
do0: jmp short do0 start
db "overflow!"
do0start:
mov ax, cs
mov ds, ax
mov si, 202h ;设置ds:si指向字符串
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx, 9 ;设置cx为字符串长度
s: mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1。
关于Debug的内容,见王爽《汇编语言》P249-P250.
int指令的格式为:int n
,n为中断类型码,它的功能是引发中断过程。
CPU执行int n
指令,相当于引发一个n号中断的中断过程。执行过程:
(ip)=(n*4),(cs)=(n*4+2)
可以在程序中使用int指令调用任何一个中断的中断处理程序。
示例1:求2 * 3456²
assume cs:code
code segment
start:
mov ax, 3456 ;(ax)=3456
int 7ch ; 调用中断7ch的中断例程,计算ax中的数据的平方
add ax, ax
adc dx, dx ;存放结果,将结果乘以2
mov ax,4c00h
int 21h
code ends
end start
示例2:
功能:将一个全是字母,以0结尾的字符串,转化为大写。
参数:ds:si指向字符串的首地址。
应用举例:将data段中的字符串转化为大写。
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
安装程序如下:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset capital
mov ax,0
mov es,ax
mov di,200h
mov cx,offset capitalend - offset capital
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
capital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
iret
capitalend:nop
code ends
end start
在系统板的ROM中存放着一套程序,称为BIOS(基本输入输出系统),BIOS中主要包含以下几部分内容。
(1)硬件系统的检测和初始化程序;
(2)外部中断和内部中断的中断例程;
(3)用于对硬件设备进行I/O操作的中断例程;
(4)其他和硬件系统相关的中断例程。
BIOS 和 DOS 在所提供的中断例程中包含了许多程序员在编程的时候需要用到的小程序。程序员在编程的时候,可以用 int 指令直接调用 BIOS 和 DOS 提供的中断例程,来完成某些工作。
和硬件设备相关的DOS中断例程中,一般都调用了BIOS的中断例程。
BIOS和DOS中断例程的安装过程
BIOS和DOS提供的中断例程是如何安装到内存中的呢?
DOS中断例程应用
示例:在屏幕的5行12列显示字符串“Welcome to masm!”
assume cs:code
data segment
db 'Welcome to masm', '$' ;“$”本身不显示,只起到边界的作用
data ends
code segment
start: mov ah, 2 ;置光标
mov bh, 0 ;第0页
mov dh, 5 ;dh中放行号
mov dl, 12 ;dl中放列号
int 10h
mov ax, data
mov ds, ax
mov dx, 0 ;ds:dx指向字符串的首地址data:0
mov ah, 9
int 21h
mov ax, 4c00h
int 21h
code ends
end start
前面讲过,各种存储器都和CPU的地址线,数据线,控制线相连,CPU操控它们的时候,把它们都当作内存来对待,把它们总地看做一个由若干个存储单元构成的逻辑存储器,我们称这个逻辑存储器为内存地址空间。
在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片。
在这些芯片中,都有一种由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片中,但是在以下两点上相同。
可见,从CPU的角度,将这些寄存器都当做端口,对他们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。
CPU可以直接读取下面三个地方的数据:
因为端口所在的芯片和CPU通过总线相连,所以端口地址和内存地址一样,通过地址总线传送。在PC系统中,CPU最多可以定位64KB个不同的端口。则端口地址的范围为0-65535。
端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。
示例:
;对0~255以内的端口进行读写时
in al, 20h ;从20h端口读入一个字节
out 20h, al ;往20h端口写入一个字节
;对256~65535的端口进行读写时,端口号放在dx中
mov dx, 3f8h ;将端口号3f8h送入dx
in al, dx ;从3f8h端口读入一个字节
out dx, al ;向3f8h端口写入一个字节
注意:在 in 和 out 指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据。
PC机中,有一个CMOS RAM芯片,一般简称为CMOS。此芯片的特征如下.
包含一个实时钟和一个有128个存储单元的RAM存储器
该芯片靠电池供电。关机后内部的实时钟正常工作,RAM中的信息不丢失
128个字节的RAM中,内部实时钟占用 0~0dh 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时BIOS程序读取。BIOS也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM中的系统信息。
该芯片内部有两个端口,端口地址为 70h 和 71h 。CPU通过这两个端口来读写CMOS RAM
70h为地址端口,存放要访问的 CMOS RAM 单元的地址;71h为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。
可见,CPU对CMOS RAM的读写分两步进行,比如,读 CMOS RAM 的2号单元:
① 将2送入端口 70h;
② 从端口 71h 读出2号单元的内容。
CMOS RAM中存储的时间信息
在CMOS RAM中,存放着当前的时间:年、月、日、时、分、秒。这6个信息的长度都为1个字节,存放单元为:
秒 | 分 | 时 | 日 | 月 | 年 |
---|---|---|---|---|---|
0 | 2 | 4 | 7 | 8 | 9 |
这些数据以BCD码的方式存放。
BCD码是以4位二进制数表示十进制数码的编码方法,如下表所示:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 |
shl和shr是逻辑移位指令。
shl是逻辑左移指令,它的功能为:
shr是逻辑右移指令,它和 shl 所进行的操作刚好相反~
mov al, 01001000b
shl al, 1 ;将a1中的数据左移一位
;执行后(al)=10010000b,CF=0。
mov al, 10000001b
shr al, 1 ;将al中的数据右移一位
;执行后(al)=01000000b,CF=1。
CPU在计算机系统中,除了能够执行指令,进行运算以外,还应该拥有I/O(输入输出)能力。
PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。
即: CPU通过端口和外部设备进行联系。
当CPU外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向CPU发出相应的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
在PC系统中,外中断源有两类:
可屏蔽中断是CPU可以不响应的外中断。CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置。
当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。可屏蔽中断过程类似于内部中断:
(IP)=(n*4),(CS)=(n*4+2)
中断过程中将IF置0的原因:在进入中断处理程序后,禁止其他的可屏蔽中断。如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1。
8086CPU提供的设置IF的指令:
2、不可屏蔽中断
不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
对于8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码。则不可屏蔽中断的中断过程为:
几乎所有由外设引发的外中断,都是可屏蔽中断。如键盘输入,不可屏蔽中断通常是在系统中有必须处理的紧急情况发生时通知CPU的中断信息。
1.键盘输入
键盘上每个按键都相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。
按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为 60h。
松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入 60h 端口中。
一般将按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。
扫描码长度为一个字节,通码的第7位为0,断码的第7位为1。
即:断码 = 通码 + 80h。
比如,g键的通码为 22h,断码为 a2h。
2.引发9号中断
键盘的输入到达60h端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。CPU检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行int 9中断例程。
3.执行 int 9 中断例程
BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要的工作如下:
BIOS 键盘缓冲区(是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区)可以存储15个键盘输入,在该缓冲区中一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。
0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下。
位 | 状态 | 置1 |
---|---|---|
0 | 右shift状态 | 置1表示按下右shift键 |
1 | 左shift状态 | 置1表示按下左shift键 |
2 | Ctrl状态 | 置1表示按下Ctrl键 |
3 | Alt状态 | 置1表示按下Alt键 |
4 | ScrollLock状态 | 置1表示Scroll指示灯亮 |
5 | NumLock状态 | 置1表示小键盘输入的是数字 |
6 | CapsLock状态 | 置1表示输入大写字母 |
7 | Insert状态 | 置1表示处于删除态 |
示例,见王爽《汇编语言》P276-P284
CPU对外设输入的通常处理方法:
端口和中断机制,是CPU进行I/O的基础。
对8086CPU的指令系统进行一下总结。读者若要详细了解8086指令系统中的各个指令的用,可以查看有关的指令手册。
8086CPU提供以下几大类指令。
讨论如何有效合理地组织数据,以及相关的编程技术。
在之前,我们一直在代码段中使用标号来标记指令、数据、段的起始地址。这些标号仅仅表示了内存单元的地址。
如:
assume cs:code
code segment
a : db 1,2,3,4,5,6,7,8 ;在a后面加有":"
b : dw 0
start :mov si,offset a
mov bx,offset b
mov cx,8
s : mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
注意:在后面加有 “ :” 的地址标号,只能在代码段中使用,不能在其他段中使用。
现在我们引出新的概念:数据标号,这种标号 不但表示内存单元的地址,还表示了内存单元的长度(即此标号处的单元,是一个字节单元还是字单元,还是双字单元)。
上面的程序现在可以写成这样:
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8 ;标号a、b后面没有":"
b dw 0
start : mov si,0
mov cx,8
s : mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
在code段中a和b后并没有 ”:” 号,这种写法同时描述内存地址和单元长度的标号。
标号a ,描述了地址 code:0 和从这个地址开始后的内存单元都是字节单元;
而标号b,描述了地址 code:8 和从这个地址开始以后的内存单元都是字单元。
注意:
如果想在代码段中直接使用数据标号访问数据,需要用伪指令 assume 将标号所在的段和一个段寄存器联系起来。否则,编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。
当然,并不是说用伪指令 assume 将标号所在的段和某个段相联系,段寄存器中就真的存放该段的地址。在程序中还要使用指令对段寄存器进行设置。
如:在程序中用ds寄存器和data段相连,则编译器对相关指令的编译如下
指令: mov al,a[si]
编译为:mov al,[si+0]
指令: add b,ax
编译为:add [8],ax