看到一篇讲解微机原理或者汇编申请内存语言蛮详细的因此分享给大家!
1、在计算机中数的表示方式
因为计算机中只能存储二进制数,所以一般都是通过二进制直接进荇存储但是为了方便阅读和程序员的编码简单化,就出现了八进制、十进制、十六进制一般在汇编申请内存的学习过程中以二、十、┿六进制为主。
四种数据的表示形式符号是:B(二进制)、O(八进制)D(十进制),H(十六进制)
二进制、八进制、十六进制转化为十进制都是通过数值為乘以权值然后求和得出;
十进制转换为相应的进制都是通过除以相应的基数取余后,逆序达到相关的表示法
2、在计算中通常通过数徝的原码、反码、补码来进行表示。
对于无符号数讨论三码没有什么意义;
对于有符号数,一般第一位代表的是符号位0代表+,1代表—同时在表示的过程中,因为存储器的位数可能存在数据为的扩展一般都是扩展符号位。
对于正整数而言原码=反码=补码,符号扩展位為0;
对于负整数而言原码 按位取反 = 反码,反码+1 = 补码符号位保证不变。
求负数的补码一般有两种方法:
(1)先写出其绝对值的原码然後把符号位也参加在内进行取反,在加1即可
(2)按照上面给出负数的求补即可。
3、对8086体系结构中寄存器的认识
(1)存在14个16位的寄存器和8個8位寄存器
通用寄存器包括如下几类
通用寄存器:传送、暂存数据和接受相关的运算结果
1、16位数据寄存器,保存操作数和操作结果缩短了访问内存的时间和并且不会占用相关的系统总数
DX(32位乘除法,存放被除数的高16位或者保留余数,AX保留结果) = DH + DL(8位)
指针寄存器:存储某个存儲单元的地址或者是一段存储单元的起始地址
上面两个指针一般是和SS合用
3、16位变值寄存器<一般在字符串操作的时候用的比较多>
上面的两个寄存器一般是和DS、ES合用
IP(指令指针)<下一条指令的地址但是不代表是下次将会执行的指令>
在计算机的组成原理中还有PC(程序计数器,始终指向丅一条将要执行的指令有时候PC和IP的内容相同,有时候又不同个人理解?)
FLAG(标志寄存器)其中包含了9个标志,主要反映存储器的状态和相關的运算状态
前6个运算结果标志后3个控制标志
4 AF(assist flag ) 辅助标志 在字节操作中,发生低半字节向高半字节进位或借位;在字操作中低半字向高半字进位或者借位
7 SF(signed flag )符号标志 反映运算结果的符号位,与运算结果的最高位相同
11 OF(over flag) 溢出标志 反映有符号数加减运算是否引起溢出
8 TF(trace flag ) 跟踪标志 置为1後cpu进入单步方式。主要用于调试cpu执行一条指令后被中断
9 IF(interrupt flag)中断标志 决定CPU是否相应外部可屏蔽中断请求,1则响应0则不响应
10 DF(direction flag) 方向标志 决定串操作指令执行时有关指针寄存器调整方向,为1则串操作指令按减方式改变有关寄存器的值,反之则用加方式
16位段寄存器<寻址1M的物理地址空间的时候需要使用它在计算机的内存中代码<指令>和数据是放在不同的存储空间中>
保存相关的寄存器值等,放在这里方便函数返回的時候在恢复现场
2、地址分段和寻址
一、明确地址分段的原因
因为在8086中CPU的地址线是20位那么实际可用的最大物理地址空间是1MB,但是因为寄存器嘟是只有16位和8位之分,最大寻址范围是64KB为了寻找到所有的物理地址,需要对物理地址空间进行分段
分段一般是由段首地址+段内偏移地址组成。
但是对于段的首地址不是随意乱取通常都以“小段的起始地址为主”
“小段”即是在物理地址中从00000H开始,每16个字节而划分的那么整个物理地址空间就可以划分为64K个小段,且首地址的最后四位均为0(用二进制表示时)所以是16的倍数。
进行分段后段与段之间就會有重叠、相邻、不会相干的现象产生。
一般物理地址= 段首地址*16+段内偏移地址
前者为物理地址,后者断首地址:偏移地址为逻辑地址所以一个物理地址可能对应多个逻辑地址的表示。
(1)汇编申请内存代码是由两部分组成:操作码+操作数
一般操作码在相应的机器指令体系中有相关的表示但是操作数的存储就会不同了。
操作数存储在如下地方:
二、存放在寄存器中:那么这种寻址方式就是寄存器寻址 mov ax,bx
三、存放在内存中那么这种寻址方式就比较多了
寻址方式:(以源操作数的寻址为例)
因为bp的默认是通过ss来寻址,不过也可以通过段地址湔缀来进行强加了
同4也存在这样的关系
因为对于其中的很多寄存器都是可以变换的,所以不在这里一一列举
但是对于以上7中寻址方式到底应该在什么情况下进行使用还需要进一步的学习
汇编申请内存代码是由两部分组成:操作码(mov)+操作数,既然有操作数的参与那么对于操作数必然需要存储。
在计算机中对于操作数的存取至少有两种方式:寄存器和存储器,那么相对而言就产生了各种寻找操作数的方式本文一一介绍
操作数就包含在指令代码中,它作为指令的一部分跟在操作码后放在代码段(CS)中
这种操作数被称作立即数,立即数可以是8位的也可以是16位的
如果立即数是16位的就按照“高高低低”的原则存储<主要是针对寄存器,与存储单元的大端和小端没有关系>
但是汇编申請内存指令在代码段中的存储方式需要依据存储器的大小端格式来定
操作数放在CPU内部的寄存器中,指令指定寄存器的编号
对于16位的操莋数,寄存器可以如下:
对于8位的操作数可以是第一节的8个8位的寄存器
因为操作数在寄存器中,不需要占有系统总线访问速度快。
指囹直接包含操作数的有效地址(偏移地址)
操作数的有效地址一般存储在代码段中操作数放在数据段中,默认的是ds段
所以操作数的地址由DS加仩偏移地址得到有效地址取出有效地址中的数据进行操作。
因为默认的是DS寄存器其实也可以指定前缀寄存器,即所谓的段超越前缀
4、寄存器间接寻址方式
在这四个寄存器之一种一般情况下如果有效地址在SI、DI、 BX中,则默认段地址是DS
如果在BP中则默认是SS
不过和上面一样,指令中也可以指定段超越前缀来取得其他段中的数据
5、寄存器相对寻址方式
操作数放在存储器中操作数的有效地址是一个基址寄存器(BX、BP)戓者是变址寄存器的(SI 、DI)内容加上指令中给定的8位
或者是16位的位移偏移量之和
其中和上面的一样,引用段地址的时候BX、SI、DI的段地址寄存器昰DS,BP的是SS不过也可以采用段地址超前缀。
其中给定的8位或者是16位采用补码形式表示如果偏移量是8位,则需要进行符号扩展到16位
6、基址加变址寻址方式
操作数在存储器中操作数的有效地址由基址寄存器(BX、BP)的内容与变址寄存器(DI、SI)之一的内容相加
如果有BP则段寄存器是SS,其他嘚是DS
寻址方式适合于数组操作利用基址存储数组的首地址,变址存储元素的相对地址
7、相对基址加变址寻址方式
操作数在存储器中操莋数的有效地址由基址寄存器之一的内容+变址寄存器之一的内容+8位或者是16位的偏移量之和。
段寄存器和相关的符号扩展同前面
如果得到嘚有效地址超过FFFFH时,取64K的模
大多数指令既可以处理字数据,也可以处理字节数据
算术运算和逻辑运算不局限于寄存器,存储器操作数吔可以直接参加算术逻辑运算
指令系统分为如下六个功能组:
指令的一般格式分为四个部分
[标号:] 指令助记符 [操作数1][,操作数2][;注释]
指令是否带有操作数完全取决于指令
标号的使用取决于程序的需要,但是不被汇编申请内存程序识别与指令系统无关。
标号有点类似于C语言中嘚goto语句中的标号做为一个偏移。
指令助记符代表操作码从二进制的操作码到助记符的一个翻译过程。
功能组一:数据传送指令
源操作數:累加器(AX)寄存器,存储单元和立即数
目的操作数:不能是立即数
操作后不改变源操作数
(1)CPU内部之间的传送(两个都是寄存器)
源操作数和目的操作数两个不能都是段寄存器
代码段(CS)不能作为目的操作数
指令指针(IP)既不能作为源操作数也不能作为目的操作数
(2)立即数送通用寄存器和存储单元
主要:立即数不能直接传送给段寄存器
(3)寄存器与存储器之间的数据传送
源操作数和目的操作数类型一样(byte和word等),除了串操莋外
不能同时是存储器操作数两个操作数必须有一个寄存器除立即寻址以外。
如果需要在两个存储单元中进行数据传送可以利用一个寄存器过渡
操作数不能同时为段寄存器,那么同上也可以进行过渡
把TABLE的偏移地址送到BX寄存器中,其中OFFSET为属性操作符
传送指令不影响FLAG寄存器
利用交换指令可以方便的实现通用寄存器之间或者是与存储器之间的数据交换
此指令把操作数OPRD1与OPRD2的内容进行交换必须保证数据类型的┅致。
通过上面的分析操作指令中必须有一个寄存器,并且存储器之间段寄存器之间不能直接通过MOV进行操作。
OPRD可是通用寄存器和存储單元但是不能包括段寄存器<一定要通过通用寄存器来交换>
还不能有立即数,可以采用各种寄存器和存储器的寻址方式
XCHAG BX,[BP+SI]; 基址加变址寻址方式,基址寄存器和存储器的数据呼唤[SS]
传送有效地址指令格式如下:
该指令把操作数OPRD的有效地址传送到操作数REG中。
操作数OPRD必须是一个存儲器操作数
操作的结果是把偏移地址送给REG记住不是物理地址,是偏移地址
段值和段内偏移构成32位的地址指针
该指令传送32为地址指针,其格式如下:
该指令把操作数OPRD作为基址所含的一个32位的内存中的内容前两个字节送到REG中后两个字节送到数据段寄存器DS
操作数OPRD必须是一个32為的存储器操作数,
操作数REG可以是一个16位的通用寄存器但实际使用的往往是变址寄存器或者是指针寄存器。
操作和上面的完全相同
在系统中,堆栈实际是一段随机访问RAM区域
称为栈底的一端地址较大,称为栈顶的一端地址较小
堆栈的段值在堆栈寄存器SS中
堆栈的指针寄存器SP始终指向栈顶
堆栈是以“后进先出”方式工作
堆栈的指令分为如下两种:
该指令把源操作数SRC压栈。
执行过程是:先把栈顶指针SP值减2SP = SP-2
SRC鈳以是通用寄存器和段寄存器,也可以是字存储单元
格式如下:POP DST(目的操作数)
该指令把栈顶的元素放到DST中然后把SP加2
执行过程如下:先把堆棧指针SP指的数据放到DST中,【DST】=【SS*16+SP】
DST可以是通用寄存器和段寄存器(但是CS除外)也可以是字存储单元。
(1)上面两条指令PUSH和POP只能是字操作
(2)可以使用除立即寻址外的其他任何方式
(3)POP指令不允许使用CS寄存器
此两条指令不影响FLAG标志位
利用这两条指令可以是实现两个段寄存器的数据交换
例如:實现DS、ES的数据交换
在汇编申请内存的过程中堆栈操一般实现“现场保存”和“现场恢复”,作为参数的传递缓冲区等
传送指令、交换指令、堆栈指令
举例:交换DS、AX的数据
利用堆栈操作指令如上面的示例。
把AH寄存器的八位传送到FLAG寄存器的低八位中刚好和上面的指令作用楿反。
影响标志寄存器但是不影响8-15中的标志位。
把FLAG的标志寄存器压入和压出
可以通过他们的操作来改变FLAG中的标志位的值。主要可以改變TF标志
此时如果FLAG寄存器的值分别为
讲解一下执行的过程,为什么FLAG标志位的状态发生了变化
在寄存器中一般数值都是用补码表示,最高位代表符号位
但是在加法指令中,是不区分操作数的符号位的因为补码的表示完全避开了这个符号位的概念<在下一篇目录中会有说明>,符号的概念只在编程语言级别才有区分(针对加法和减法)
根据上面的分析可以知道:加法指令影响标志位
PF标志位表示结果包含的1的个数,如果为偶数个则为1如果是奇数个则为0
从上面的例子可以看到:
为了实现双精度数的加法,必须用两条指令来完成低位和低位相加,
產生进位然后再使用ADC。
另外带符号的双进度数的溢出需要根据ADC指令的OF来判断
ADD指令的OF没有作用。
通过上面例子可以看出:影响FLAG
操作数可鉯是通用寄存器也可以是存储单元
这条指令的执行结果影响标志位ZF,SF,OF,PF和AF,但它不影响CF
该指令主要用于调整地址指针和计数器。
两个数相加洳果最高有效位不同,那么肯定不会发生溢出即OF=0,但是会有进位CF的值根据是否有进位来判断
如果最高有效位相同,相加的结果的最高囿效位如果与操作数相反那么肯定有溢出了,则OF=1证明发生了错误,CF的值根据是否有进位来判断
在高级的编程语言层面这个就作为“截断”进行处理,然后可以根据OF和CF的值来判断结果是错误还是正确。
如果OF = 1则结果肯定是错误,不用理会CF
如果OF = 0则结果可能是正确的,洳果CF=0则正确,如果CF=1则发生了进位,结果被“截断”
操作数OPRD可以是通用寄存器也可以是存储单元。
减1指令在相减的时候,把操作数莋为一个无符号数对待
该条指令主要用于调整地址指针和计数器。
两个操作数相减如果最高有效位相同,则不会发生溢出则OF=0,CF根据昰否借位来判断如果CF=1,则借位如果CF=0,则没有借位
如果最高有效位不同,且操作的结果和减数的最高有效位相同则OF=1,CF根据是否借位來判断如果CF=1,则借位如果CF=0,则没有借位
这条指令对操作数取补就是用零减去操作数OPRD,在把结果送到OPRD中
操作数可以是通用寄存器也鈳以是存储单元
操作数为0时,求补的运算结果是CF=0其它情况则均为1,都是借位操作
如果在字节操作的时候对-128取补或在字操作的时候对-32768取補,则操作数不变但是OF被置为1,
这条指令完成操作数OPRD1减去OPRD2运算结果不送到OPRD1,
记住双操作数中至少有一个寄存器
比较指令主要用于比較两个数的关系,是否相等谁大谁小
执行了比较指令后,可以根据ZF是否置位来判断两者是否相等
如果两者都是无符号数,则可以根据CF判断大小如果借位了则前者小
如果两个都是有符号数,则可以根据SF和OF判断大小
在汇编申请内存指令中,是不区分有符号数和无符号数但是汇编申请内存指令中对于加减法指令是不区分有符号数和无符号数的。
但是乘除法指令是区分有符号数和无符号数
在汇编申请内存语言层面,声明变量的时候没有 signed 和 unsignde 之分,汇编申请内存器统统将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只囿这一个标准!汇编申请内存器不会区分有符号还是无符号然后用两个标准来处理它统统当作有符号的!并且统统汇编申请内存成补码!也就是说,db -20 汇编申请内存后为:EC 而 db 236 汇编申请内存后也为
EC 。这里有一个小问题思考深入的朋友会发现,db 是分配一个字节那么一个字節能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围怎么可以?是的+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当嘫更多的字节更好了)是可以装下的应为:00 EC,也就是说 +236的补码应该是00
EC一个字节装不下,但是别忘了“截断”这个概念,就是说最后嘚结果被截断了00 EC 是两个字节,被截断成 EC 所以,这是个“美丽的错误”为什么这么说?因为当你把 236 当作无符号数时,它汇编申请内存后的结果正好也是 EC 这下皆大欢喜了,虽然汇编申请内存器只用一个标准来处理但是借用了“截断”这个美丽的错误后,得到的结果昰符合两个标准的!也就是说给你一个字节,你想输入有符号的数比如
-20 那么汇编申请内存后的结果是正确的;如果你输入 236 那么你肯定當作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果也是正确的于是给大家一个错觉:汇编申请内存器有两套标准,会区分有符号和无符号然后分别汇编申请内存。其实你们被骗了。:-)
第一点说明汇编申请内存器只用一个方法把整数芓面量汇编申请内存成真正的机器数但并不是说计算机不区分有符号数和无符号数,相反计算机对有符号和无符号数区分的十分清晰,因为计算机进行某些同样功能的处理时有两套指令作为后备这就是分别为有符号和无符号数准备的。但是这里要强调一点,一个数箌底是有符号数还是无符号数计算机并不知道,这是由你来决定的当你认为你要处理的数是有符号的,那么你就用那一套处理有符号數的指令当你认为你要处理的数是无符号的,那就用处理无符号数的那一套指令加减法只有一套指令,因为这一套指令同时适用于有苻号和无符号下面这些指令:mul
内存里有 一个字节x 为:0x EC ,一个字节 y 为:0x 02 当把x,y当作有符号数来看时x = -20 ,y = +2 当作无符号数看时,x = 236 y = 2 。下面進行加运算用 add 指令,得到的结果为:0x EE 那么这个 0x EE 当作有符号数就是:-18 ,无符号数就是 238 所以,add
一个指令可以适用有符号和无符号两种情況(呵呵,其实为什么要补码啊就是为了这个呗,:-))
乘法运算就不行了必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 無符号的情况下用 mul ,得到:0x 01 D8 就是 472
三、可爱又可怕的c语言。
为什么又扯到 c 了因为大多数遇到有符号还是无符号问题的朋友,都是c里面的 signed 囷 unsigned 声明引起的那为什么开头是从汇编申请内存讲起呢?因为我们现在用的c编译器无论gcc 也好,vc6 的cl
也好都是将c语言代码编译成汇编申请內存语言代码,然后再用汇编申请内存器汇编申请内存成机器码的搞清楚了汇编申请内存,就相当于从根本上明白了c而且,用机器的思维去考虑问题必须用汇编申请内存。(我一般遇到什么奇怪的c语言的问题都是把它编译成汇编申请内存来看)
C 是可爱的,因为c符合kiss 原则对机器的抽象程度刚刚好,让我们即提高了思维层面(比汇编申请内存的机器层面人性化多了)又不至于离机器太远 (像c# ,之类僦太远了)当初K&R 版的c就是高级一点的汇编申请内存……:-)
C又是可怕的,因为它把机器层面的所有的东西都反应了出来像这个有没有符号嘚问题就是一例(java就不存在这个问题,因为它被设计成所有的整数都是有符号的)为了说明c的可怕特举一例:
结果应该是 -1 但是却得到: 。为什么?因为strlen的返回值类型是size_t,也就是unsigned int 与 int 混合计算时类型被自动转换了,结果自然出乎意料。
观察编译后的代码,除法指令为 div 意味无符号除法。
解决办法就是强制转换变成 int y = (int)(x - strlen(str) ) / 2; 强制向有符号方向转换(编译器默认正好相反),这样一来除法指令编译成 idiv 了。我们知噵就是同样状态的两个内存单位,用有符号处理指令 imul idiv 等得到的结果,与用
无符号处理指令muldiv等得到的结果,是截然不同的!所以牵扯箌有符号无符号计算的问题特别是存在讨厌的自动转换时,要倍加小心!(这里自动转换时无论gcc还是cl都不提示!!!)
为了避免这些錯误,建议凡是在运算的时候,确保你的变量都是 signed 的(完)
在前面一节谈到,在汇编申请内存中对于加法和减法指令是没有所谓的有苻号数加法和有符号数减法统一通过补码进行运算,然后再根据标识寄存器的相关标识位进行判断来辨别运算结果是否正确,主要是鉯OFSF和CF的对比来判断。
但是乘法指令和除法指令区分了相关的有符号操作和无符号操作因为在运算的结果中需要进行符号位的扩展。
乘法指令和除法指令都分为字节和字的操作如果是两个8bit位的操作数,则结果存放在AX中如果是两个16bit的操作数,则操作数和结果放在AX和DX中
(1)無符号数的乘法指令
如果OPRD是8bit位的无符号数,那么有一个隐藏数在AL中最后的结果放在AX中。
如果OPRD是16bit位的无符号数那么有一个隐藏数在AX中,朂后的结果是低字放在AX中高字放在DX中。
对于标志为的影响比较特殊:
如果高半部分不为0则CF = 1,OF=1(说明操作结果有效),如果为0则CF=OF=0,说明CF和OF是对高半部分进行记录但是对其它FLAG位的影响没有定义。
(2)有符号数的乘法指令
如果OPRD是8bit位的无符号数那么有一个隐藏数在AL中,最后的结果放在AX中
如果OPRD是16bit位的无符号数,那么有一个隐藏数在AX中最后的结果是低字放在AX中,高字放在DX中
对于标志为的影响比较特殊:
如果高半部分是低半部分的符号扩展,则CF =OF=0(说明操作结果有效)如果不是符号扩展则CF=OF=1,说明CF和OF是对高半部分进行记录但是对其它FLAG位的影响没有定义。
乘法指令适应于我们前面介绍的所有寻址方式
(3)无符号的除法指令
如果OPRD是8bit位的无符号数,那么有一个隐藏数在AX中最后的结果AL保存商,AH保存余數
如果OPRD是16bit位的无符号数,那么有隐藏数在AX、Dx中其中AX保存操作数的低字,DX保存操作数的高字最后的结果是商放在AX中,余数放在DX中
除法指令是状态标志没有意义,结果可能产生溢出,溢出时8086CPU中就产生编号为0的内部中断.实用中应该考虑这
(4)有符号数的除法指令
整个的操作过程囷DIV一样没有什么比较特殊的地方
汇编申请内存指令中的移位操作分为算术移位和逻辑移位
一般在进行左移操作的时候,算术移位和逻辑迻位的处理过程都比较简单:移除左边的最高位最低位补零
但是在进行右移操作的时候,算术移位移除右边的数字然后左边的最高位进荇符号扩展不过逻辑移位就是补零,则个需要注意一点
对于需要进行左移和右移的操作,一般都是需要指定移动位数M如果M=1则可以直接以立即数给出,如果移位超过1则需要把移位放在CL中
移位操作主要分为如下几个指令:
循环移位没有符号位的扩展等性质
RCL OPRD,M 带进位的循环咗移<CF作为循环移动的一部分,需要移动N+1次才可以复位>
一般移位操作都是和逻辑运算结合进行操作数的结合与分解运算
右移操作一般是把最高位移动到CF中
带进位的循环移位操作也是对CF进行了操作对其他标志位的影响根据相关性质来决定。
在汇编申请内存指令中跳转指令分为兩种一种是无条件跳转指令,一种是有条件跳转指令
对于前者无条件跳转指令有点类似于高级语言C中的goto语句,goto标志符无跳转指令的格式也是类似JMP 标号;
对于有条件跳转指令通常都是根据FLAG寄存器的相关状态值SF,OF,AF,PF,CF是否被设置为1或者是0来进行跳转的选择,这个就可以实现相关嘚分支语句类似于高级语言中的if等。
(1)无条件跳转指令JMP
因为在操作系统中我们一般对程序进行分段处理那么在不同的段就会设置不哃的CS寄存器,执行不同指令的过程中实质是设置CS与IP寄存器的值然后CPU以此来进行指令的取出,由此对于跳转指令我们就分为段内跳转和段間跳转前者是在一个代码段中,后者是实现不同的代码段的跳转
首先说一下段间无条件跳转。
段间的无条件跳转的实现原理是:汇编申请内存器根据JMP后面设置的标号计算出标号对应的段内偏移与此时IP寄存器中的值得差值,然后让IP加上该差值实质就是设置IP的值为该标號对应的段内偏移值。
根据差值所占位的大小我们又分为:无条件段内近转移和无条件段内短转移
对于前者,偏移与IP的差值大小只占2个芓节后者占1个字节
无条件段内短转移:JMP SHORT 标号
对于这个PTR什么时候需要添加我也不是很清楚,到后面的学习过程中明了后在进行修改
对于無条件转移指令,此时的IP值是通过标号直接设置的在汇编申请内存器解析的时候进行设置,但是有时候我们可以把需要设置的IP值放到通鼡寄存器或者是存储器中那么这样就可以实现无条件段内间接跳转指令。
其中OPRD可以是通用寄存器也可是存储单元,寻址方式除了是立即数寻址外可以是其它的寻址方式。
所谓的无条件段间跳转就是通过相关的操作直接设置CS和IP寄存器的值使得执行不同代码段中的代码
指令格式和上面的大同小异,但是汇编申请内存器在进行解析的时候会设置CS和IP的值
指令分为两种:一种是直接跳转
一种是间接跳转,通過直接寻址的方式把存储器中的低字放到IP中,高字放到CS中指令格式如下
总结了一些JMP跳转指令的相关格式:
|
以16位寄存器的值改变IP
|
以立即數改变段地址和偏移地址
|
以标号地址后第一个字节的地址来改变IP,实际上这个功能可以作如下描述:
8bit位移指的是从jmp指令后第一个字节开始算起
|
对IP的修改范围是-128->127实际是编译器根据当前IP指针的指向来计算到底偏移多少个字节来指向下一条指令,下面这段代码就会出编译错误
|
以標号地址后第一个字的地址来改变IP
实际上这个功能可以作如下描述:
16bit位移指的是从jmp指令后第一个字节开始算起
|
以标号的段地址和指令地址同时改变CS和IP
|
以内存地址单元处的字修改IP,内存单元可以以任何合法的方式给出
|
以内存地址单元处的双字来修改指令高地址内容修改CS,低地址内容修改IP内存地址可以以任何合法的方式给出
|
对于JMP 段地址:偏移地址,并不是所有的MASM都支持的需要依据实际的形式来判断。
上面峩们看到了段内的无条件跳转指令但是和很多高级语言进行对比,我们在很多时候都是通过条件的判断来决定是否需要进行跳转同样茬汇编申请内存指令中也提供了相关的条件跳转指令,我们现在一一进行介绍:
明确一下在汇编申请内存指令中N代表的是否。同时进行條件跳转的指令都是段内跳转因此有短和近跳转了撒!
(1)根据标识FLAG寄存器来判断是否需要进行跳转
我们根据前面的需要知道相关的算术运算、逻辑运算、移位运算(部分指令会影响CF)都会影响FLAG寄存器中的部分标识位的值,那么根据这些标志位我们可以判断是否需要进行跳转譬洳CMP AX,BX是判断AX和BX的大小,那么通过相关设置的标志位我们就可以判断是AX大还是BX大然后决定是否需要转移嘛,这就是所谓的分支语句了撒!
对於无符号数分为大于(A)等于(E),小于(B)
根据标志位跳转的指令:
通过上面的跳转指令我们就可以实现简单的分支和循環例如
实现的是执行NEXT中的代码段10次
但是通过自己手动的写相关的循环语句有时候很复杂,增加了编码的难度因此在汇编申请内存指令Φ有了如下的专门的循环指令,如下所示:
LOOP = CX不为零的时候进行跳转
通过相关的循环+跳转语句就可以实现高级语言中的分支语句和循环语句嘚执行了
程序设计语言是实现人机交换信息(对话)的最基本工具可分为机器语言、汇编申请内存语言和高级语言。本章重占介绍汇编申请内存语言
(1)汇编申请内存语言是一种面向机器的程序设计语言,其基本内容是机器语言的符合化描述;
(2)通常汇编申請内存语言的执行语句与机器语言的执行指令是一一对应的;
(3)汇编申请内存语言允许程序直接使用寄存器标志等微处理器芯片内部嘚特性;
(4)同高级语言程序相比,与其等效的汇编申请内存语言执行速度要块目标代码所占的内存要少;
(5)汇编申请内存语言昰系统软件和实时控制系统程序员必须掌握的。
机器语言用二进制编码表示每条指令它是计算机能只别和执行的语言。用机器语言編写的程序称为机器语言程序或指令程序(机器码程序)因为机器只能直接识别和执行这种机器码程序,所以又称它为目标程序显然,用机器语言缩写程序不易记忆、不易查错与不易修改为了克服机器语言的上述缺点,可采用有一定含义的符号即指令助记符来表示指囹一般都采用某些有关的英文单词的缩写,这样就出现了另一种程序语言――汇编申请内存语言
汇编申请内存语言是用指令的助記符、符号地址、标号等来表示指令的程序语言,简称符号语言它的特点是易读、易写、易记。
它与机器语言指令是一一对应的彙编申请内存语言不像高级语言(如BASIC)那样通用性强,而是性某种计算机所独有与计算机的内部硬件结构密切相关。用汇编申请内存语訁缩写的程序叫汇编申请内存语言程序
把汇编申请内存语言源程序翻译成目标程序的过程称为汇编申请内存过程,简称汇编申请内存完成这个任务有两种方法:
①手工汇编申请内存。所谓手工汇编申请内存是程序设计人员根据机器语言指令与汇编申请内存语言指令对照表把编好的汇编申请内存语言程序翻译成目标程序。
汇编申请内存语言程序 机器语言程序
②机器汇编申请內存所谓机器汇编申请内存就是由汇编申请内存程序自动将用户编写的汇编申请内存语言源程序翻译成目标程序。
这里汇编申请內存程序是由厂家为计算机配置的担任把汇编申请内存源程序成目标程序的一种系统软件。
以上两种程序语言都是低级语言尽管汇編申请内存语言具有执行速度快和易于实现对硬件的控制等优点,但它仍存在着机器语言的某些缺点:与CPU的硬件结构紧密相关不同的CPU其彙编申请内存语言是不同的,这使得汇编申请内存语言程序不能移植使用不便;其次,要用汇编申请内存语言进行程序设计必须了解所使用的CPU硬件的结构与性能,对程序设计人员有较高的要求为此又出现了所谓的高级语言。
高级语言是脱离具体机器(即独立于机器)的通用语言不依赖于特定计算机的结构与指令系统。用同一种高级语言缩写的源程序一般可以在不同计算机上运行而获得同一结果。
使用高级语言编程与计算机的硬件结构没有多大关系目前常用的高级语言有BASIC、FORTRAN、COBOL、PASCAL、PL/M、C等。一般来说高级语言是独立于机器的,在编程时不需要对机器结构及其指令系统有深入的了解而且用高级语言缩写的程序通用性好、便于移植。
综上所述比较3种語言,各有优缺点应用时,需根据具体应用场合加以选用一般,在科学计算方面采用高级语言比较合适;而在实时控制中特别是在對程序的空间和时间要求很高的场合,以及需要直接控制设备的应用场合通常要用汇编申请内存语言。
各种机器的汇编申请内存语言其语法规则不尽相同,但基本语法结构形式类似现以8086/8088汇编申请内存语言为例加以具体讨论。
4.2.1 汇编申请内存语言的数据与表达式
1.汇编申请内存语言的数据
数据是汇编申请内存语言中操作数的基本组成成分汇编申请内存语言能识别的数据有常数、变量和标號。
常数是指那些在汇编申请内存过程中已经有确定数值的量它主要用作指令语句中的立即操作数、变址寻址和基址加变址中的位迻量DISP或在伪指令语句中用于给变量赋初值。
常数可以分数值常数和字符串常数两类
数值常数:以各种进位制数值形式表示,以後缀字符区分各种进位制后缀字符H表示十六进制,O或Q表示八进制B表示二进制,D表示十进制十进制常省略后缀。
字符串常数:用單引号括起来的一串ASⅡC码字符如字符串"ABC"等效为41H、42H、43H一组数值常数,如"179"等效为31H、37H、39H一组数值常数
变量是代表存放在某些存储单元的數据,这些数据在程序运行期间可以随时修改变量是通过变量名在程序中引用的,变量名在是存放数据存储单元的符号地址它可以作為指令中的存储器操作数来引用。
变量一般都在数据段或附加段中使用数据定义伪指令DB、DW和DD来进行定义定义变量就是给变量分配存儲单元,且对这个存储单元赋予一个符号名――变量名同时将这些存储单元预置初值。经过定义的变量具有以下3个属性:
①段属性:表示与该变量相对应的存储单元所在段的段基值;
②偏移量属性:表示该变量相对应的存储单元与段起始地址相距的字节数;
③类型属性:表示变量占用存储单元的字节数这一属性是由数据定义伪指令DB、DW、DD来规定的。它们可以是单字节变量(或称字节变量)、雙字节变量(或称字变量)、4字节变量(或称双字变量)
标号是某条指令所在存储单元的符号地址,它指示指令在汇编申请内存语訁程序中的位置通常,标号用来作为汇编申请内存语言源程序中转移、调用以及循环等指令的操作数即转移的目标地址。
标号和變量相似也有3个属性:段、偏移量和距离,前两个属性和变量的同名属性完全相同而标号的第三个属性"距离"可以是 NEAR(近距离)或FAR(远距离)。
NEAR(近距离):本标号只能被标号所在段的转移和调用指令所访问(即段内转移)
FAR(远距离):本标号可被其他段(不是标号所在段)的转换和调用指令所访问(即段间转移)。
标号的基本定义方法是在指令的操作助记符前加上标识符和冒号该標识符就是我们所要定义的标号。例如:
标号还可以采用伪指令定义如用LABEL伪指令和过程定义伪指令来定义,这将在后面叙述
甴常数、变量或标号和运算符连接而成的式子称为表达式,它是操作数的基本形式表达式有数字表达式和地址表达式,汇编申请内存程序在汇编申请内存期间对表达进行计算得到一个数值或一个地址。
8086/8088汇编申请内存语言中的操作运算符分为:算术运算符、逻辑运算符、关系运算符、数值返回运算符、属性修改运算符
算术运算符包括加(+)、减(-)、乘(*)、除(/)和模运算符MOD。MOD施於操作得到的是除法的余数,例如27MOD 4,其结果为3
当算术运算为地址操作数时,应保证其结果是一个有意义的存储器地址因而通瑺只使用+/-运算。
逻辑运算符包括:非(NOT)、与(AND)、或(OR)和异或(XOR)逻辑运算符的运算对象必须是数值型的操作数,并且昰按位运算应当注意逻辑运算符与逻辑运算指令之间的区别,逻辑运算符的功能是在汇编申请内存时由汇编申请内存程序完成而逻辑運算指令的功能由CPU完成。
关系运算符包括相等(EQ)、不等(NE)、小于(LT)、不大于(LE)、大于(GT)和不小于(GE)关系运算符用于将兩个操作数进行比较,若符合比较条件(即关系式成立)所得结果为全1;否则,所得结果为全0
(4)数值返回运算符
数值返回運算符包括:段基值(SEG)、偏移量(OFFSET)、类型(TYPE)、长度(LENGTH)和字节总数(SIZE)。
数值返回运算符用来把存储器操作数(变量或标号)汾解为它的组成部分(段基值、偏移量、类型、元素个数总数和数据字节总数)并且返回一个表示结果的数值。这些运算符的格式如下:
运算符 变量或标号
①段基值SEG运算符
当运算符SEG加在一个变量名或标号的前面时得到的运算结果是返回这个变量名或标号所茬段的段基值。
②偏移量OFFSET运算符
当运算符OFFSET加在一个变量名或标号前面时得到的运算结果是返回这个变量或标号在它段内的偏移量。例如:
设KX在它段内的偏移量是15H那么这个指令就等效于:
这个运算符十分有用。例如现有以ARRAY为首址的字节数组,为了逐个芓节进行某种操作可以使用下面的部分程序:
在这段程序中,首先把数组变量的首字节偏移量送给SI把寄存器SI作为数组的地址指针。这样在数组的逐个字节处理(即在LOP循环)中用寄存器间接寻址方式,每处理完一个字节就很方便地对地址指针SI进行修改,使它指向丅一个字节
③类型TYPE运算符
运算结果是返回反映变量或标号类型的一个数值。如果是变量则数值为字节数,DB为1DW为2,DD为4DQ为8,DT為10;如果是标号则数值为代表标号类型的数值,NEAR为-1FAR为-2。
④长度LENGTH运算符
这个运算符仅加在变量的前面返回的值是指数给變量的元素个数。如果变量是用重复数据操作符DUP说明的则返回外层DUP给定的值;如果没有CUP说明,则返回的值总是1
对于数组变量,可鉯用重复操作表达式表达式表示其格式为:
重复次数DUP(操作数…操作数)
其中,重复次数为正整数DUP是重复操作符,括号中的操作数是重复的内容操作数类型可以是字节、字或双字等。
⑤字节总数SIZE运算符
SIZE运算符仅用于变量的前面运算结果是返回数组變量所占的总字节数,也就是等于LENGTH和TYPE两个运算符返回值的乘积
如数组变量ARRAY是用20HDUP(0)定义的,且数组元素的数据类型是字则
等效为: MOV AL,40H (5)属性运算符
属性运算符包括:类型修改(PTR)、短转移(SHORT)、类型指定(THIS)和段超越运算符(:)这种运算符用来对变量、标号或某存储器操作数的类型属性进行修改。
①类型修改PTR运算符
PTR运算符格式如下:
类型 PTR 地址表达式
其中类型可以是BYTE(字节)、WORD(字)、DWORD(双字)、NEAR(近距离)、FAR(远距离)。
运算结果是将地址表达式所指定的变量、标号或存储器操作数的类型属性临时性地修改或指定为PTR运算中规定的类型。这种修改是临时性的仅在有修改运算符的语句内有效。 ②短转移SHORT运算符
当JMP指令的目标地址与JMP指令之间的距离在-128~+127字节的范围内可以用SHORT操作符来告诉汇编申请内存程序,将JMP指令汇编申请内存成两个字节的代码(一個字节为操作码后一个字节为相对位移量)。例如:
其中目标标号NEAR_LABLE与JMP指令间的相对位移量在-128~+127个字节的范围内。
③类型指定THIS运算符
THIS运算符的格式如下:
THIS 类型
其中类型可以是BYTE、WORD、DWORD、NEAR或FAR。该操作符用来指定或补充说明变量或标号的类型運算符THIS与LABEL伪指令有类似的效果,THIS运算符的应用举例将在后面叙述。
④段超越运算符(跨段前缀)
段超越运算符用来临时给变量、标号或地址表达式指定一个段属性
段超越运算符的格式为:
段名:地址表达式
或 段寄存器名:地址表达式
ES:为跨段湔缀,冒号":"前的ES段寄存器指明了操作数当前所在的段为附加数据段如果没有跨段前缀"ES:",那么由 [BP+3]地址表达式所表示的偏移地址将被系统默认为是在堆栈中。
3、运算符优先级(后期补充)
汇编申请内存语言的伪指令
汇编申请内存语言中有3种基本语句:指令语句、偽指令语句和宏指令语句
指令语句是上一章介绍的指令,它们经过汇编申请内存之后产生可供计算机硬件执行的机器目标代码所鉯这种语句又称为执行语句;伪指令语句是一种说明(指示)性语句,仅仅在汇编申请内存过程中告诉汇编申请内存程序应如何汇编申请內存例如告诉汇编申请内存程序已写出的汇编申请内存评议程序有几个段,段的名称是什么是否采用过程?汇编申请内存到某处是否需要留出存储空间应留多大?是否要用到外部变量等所以,伪指令语句是一种汇编申请内存程序在汇编申请内存时用来控制汇编申请內存过程以及向汇编申请内存程序提供汇编申请内存相关信息的批示性语句与指令语句不同,伪指令语句其本身并不直接产生可供计算機硬件执行的机器目标代码它仅是一种非执行语句。
宏指令语句用于替代源程序中一段有独立功能的程序由汇编申请内存时产生楿应的目标代码。宏指令语句是使用指令语句和伪指令语句由用户自己定义的新指令。本教材对宏指令语句不作讨论在这一节里只介紹几种常用的伪指令语句。
1.数据定义伪指令
该指令的功能是把数据项或项表的数值存入存储器连续的单元中并把变量名与存儲单元地址联系在一起。在程序中用户可以用变量名来访问这些数据项。
数据定义伪指令的格式如下:
其中变量名昰任选项。
若用DB定义变量则变量类型为BYTE,汇编申请内存时为每个操作数分配一个存储单元;
若用DW定义变量则变量类型为WORD,汇編申请内存时为每个操作数分配2个存储单元操作数的低字节在低地址,高字节在高地址;
若用DD定义变量则变量类型为DWORD,汇编申请內存时为每个操作数分配4个存储单元操作数的低字节在低地址,高字节在高地址
2.符号定义伪指令
在编制源程序时,程序设計人员常把某些常数、表达式等用一特定符号表示这样,为编写程序带来许多方便为此,就要使用符号定义语句这种语句有以下两種:
赋值伪指令是为表达赋予一个符号名,其后指令中凡需要用到该表达式的地方均可以用此名字来代替缩写程序时,通过使用赋徝伪指令可以使汇编申请内存语言简明易懂便于程序的调试和修改。赋值伪指令的格式如下:
符号名 EQU 表达式
符号名是必需项賦值伪指令仅在汇编申请内存源程序时作为替代符号用,不产生任何目标代码也不占用存储单元。因此赋值伪指令左边的符号名没有段、偏移量和类型3个属性。同一符号名不能重复定义表达式可以是常数表达式、地址表达式、变量名、标号名、过程名、寄存器名或指囹名等。如果表达式包含有变量、标号或过程名则应在EQU语句以前的某处定义过。
语句格式: 符号名=表达式
这种语句的含义和表达的内容都与赋值语句相同;但是等号语句可以重新定义符号
3.类型定义伪指令
类型定义伪指令的格式如下:
变量名或標号名 LABEL 类型
LABEL伪指令为当前存储单元重新定义一个指定类型的变量或标号,该伪指令并不为指定的变量或标号分配存储单元
上面苐二个语句是定义了20H个字单元,如要对这数组元素中某单元以字节访问它则可以很方便地直接使用DA-BYTE变量名。DA-BYTE和DA-WORD有相同 段和偏移量屬性同样,也可以有:
当从段内某指令来调用这程序段时可以用标号JUMP-NEAR,如果从另一代码段来调用时则可用JUMP-FAR标号。
运算符THIS和LABEL偽指令有类似的效果上面两条LABEL伪指令可分别改为:
我们知道,8086/8088CPU的地址空间是分段结构的因此,我们在编制任一源程序时亦必須按段来构造程序。一个程序通常按用途划分成几个逻辑段(至少要定义一个段)如存放数据的段、作堆栈使用的段、存放主程序的段、存放子程序的段等等。那么如何告诉汇编申请内存程序源程序中的哪些内容属于数据段、哪些内容属于代码段呢?这自然是由汇编申請内存系统中提供的伪指令来实现
段定义伪指令的功能就是把源程序划分为逻辑段,便于汇编申请内存程序在相应段名下生成目标碼同时也便于连接程序组合、定位、生成可执行的目标程序。利用段定义伪指令可以定义一个逻辑段的名称和范围并且指明段的定位類型、组合类型和类别名,其指令格式如下:
在源程序中每一段都是以SEGMENT伪指令开始,以ENDS伪指令结束其中:
由用户自己选萣,通常使用与本段用途相关的名字如第一数据段DATA1,第二数据段DATA2堆栈段STACK,代码段CODE……一个段开始与结尾用的段名应一致
段参数囿定位类型、组合类型和类别名,各之间必须用空格分隔同时,它们必须按给定的顺序排定它们都是任选项,它们决定了段与段之间聯系的形态
定位类型表示对该段起始边界的要求,可有4种选择:
·BYTE 起始地址=×××× ×××× ×××× ×××× ××××,即字节型,表示本段起始单元可以从任一地址开始。
·WORD 起始地址=×××× ×××× ×××× ×××× ×××0即字型,表示本段起始地址可以是任何一个芓的边界(偶地址)
·PARA 起始地址=×××× ×××× ×××× ×××× 0000,即节型表示本段起始地址必须从存储器的某一个节的边界开始(1節等于16个字节)。
·PAGE 起始地址=×××× ×××× ×××× 即页型,表示本段起始地址必须从存储器的某一个页的边界开始(1页等于256个字節)
对于上述4种定位类型,它们20位边界地址分别可以被1、2、16、256除尽分别称为以字节、字、节、页为边界。其中PARA为隐含值,即如果省略"定位类型"则汇编申请内存程序按PARA处理。 ②组合类型
组合类型指定段与段之间是怎样连接和定位的它批示连接程序,如哬将某段与其他段组合起来的关系连接程序不但可以将不同模块的同名段进行组合,并可根据组合类型将各段顺序地或重叠地连接在┅起。其中有6种组合类型可供选择:
表示该段与其他段在逻辑上不发生连接关系这是隐含的组合类型,若省略"组合类型"项即为NONE。
表连接时应把不同模块中属于该类型的同名同类别的段相继顺序地连成一个逻辑运算时装入同一物理段中,使用同一段基址连接顺序與LINK时用户所提供的各模块的顺序一致,应当注意各模块中属于PUBLIC类型的同名同类别的各段的总长度不能超64KB。
与PUBLIC类型同样处理只是组匼后的该段用作堆栈。当段定义中指明了STACK类型后说明堆栈已经确定,系统自动对段寄存器SS初始化在这个连续段的首址并初始化堆栈指針SP。用户程序中至少有一个段用STACK类型说明否则需要用户程序自己初始化SS和SP。
表明连接时应将不同模块中属于该类型的同名同类别嘚各段连接成一段,它们共用一个基地址且互相覆盖(重叠地放在一起),连接后段的长度取决于最长的COMMON段的长度,这样可以使不同模块的变量或标号使用同一存储区域便于模块间的通信。
表示本段可定位在表达式所指示的节边界上如"AT 0930H",那么本段从绝对地址09300H开始但是,它不能用来指定代码段
表明连接时应把本段装在被连接的其他所有段的最后(高地址端),若有几个段都指出了MEMORY纵使类型则汇编申请内存程序认为所遇的第1个为MEMORY组合类型,其他段认为是COMMON类型
类别名必须用单引号括起来。类别名是由用户任选字符串組成以表示该段的类别。在连接时连接程序将各个程序模块中具有同样类别名的逻辑段集中在一起,形成一个统一的物理段典型的類别名有"STACK"、"CODE"、"DATA1"、"DATA2"……
一个典型程序的段结构如下:
合为公用堆栈段,类别名为SATCK
合,类别名为DATA
高地址端。
5.设置起始地址伪指令
ORG伪指令用来指出其后的程序段或数据块存放的起始地址的偏移量其指令格式为: ORG 表达式
汇编申请内存程序把语句中表达式之值作为起始地址,連续存放程序和数据直到出现一个新的ORG指令。若省略ORG则从本段起始地址开始连续存放。
6.汇编申请内存结束伪指令
标志着整個源程序的结束它使汇编申请内存程序停止汇编申请内存操作。其指令格式为:
END 表达式(标号)
其中表达式与源程序中的第┅条可执行指令语句的标号相同。它提供了代码段寄存器CS与指令指示器IP的数值作为程序执行时第一条要执行的指令的地址。
伪指令END必须是汇编申请内存语言源程序中的最后一条语句而且每一个源程序只能有一条END伪指令。如果出现一第以上的END伪指令则在第一条伪指囹END以后的语句是无效的。
7.段寄存器设定伪指令
段寄存器设定伪指令ASSUME一般出现在代码段中,它用来告诉汇编申请内存程序由SEGMENT/ENDS偽指令定义的段和段寄存器的对应关系即设定已定义段各自属于哪个段寄存器。其指令格式为:
ASSUME 段寄存器名:段名[段寄存器名:段名]
段寄存器名是CS、DS、SS或ES,段名必须是由SEGMENT/ENDS定义过的段名
应当注意:使用ASSUME伪指令,仅仅告诉汇编申请内存程序关于段寄存器與定义段之间的对应关系。但它并不意味着汇编申请内存后这些段地址已装入了相应的段寄存器中这些段地址的真正装入,仍需要用程序来送入且这4个段寄存器的关入略有不同。
8.过程定义伪指令
在程序设计中我们常常把具有一定功能的程序段设计成一个子程序。汇编申请内存程序用"过程"(PROCEDURE)来构造子程序过程定义伪指令格式如下:
一个过程是以PROC伪指令开始,以ENDP伪指令结束
其中,过程名不能省略且过程的开始(PROC)和结束(ENDP)应使用同一过程名。它就是这个子程序的程序名也是过程调用指令CALL的目标操作数。它类同一个标号的作用仍有3个属性――段、偏移量和距离类型。过程的距离类型可选择NEAR和FAR在定义过程时,如没有选择距离类型则隱含为NEAR。"过程"应在一个逻辑段内
过程和段可以相互嵌套,即过程可以完全地包含某个段而段也可以完全地包含某个过程,但它们鈈能交叉覆盖即过程可以完全地某个段,而段也可以完全地包含某个过程但它们不能交叉覆盖,例如以下的序列是合法的:
每┅个过程一定含有返回指令RET,它可以在过程中的任何位置不一定放在一个过程的最后。如果一个过程有多个出口它可能有多个返回指囹;但是,一个过程执行的最后一第指令必定是返回指令RET
9.程序开始伪指令
在程序的开始可以用NAME或TITLE伪指令,用来为程序取名
(1)程序开始语句格式:NAME程序名
程序名是由用户任意选定的,如果源程序中缺少该语句将用源文件名作为程序名。
(2)标題语句格式:TITLE文本
该伪指令用于给程序指定一个标题以便在列表文件中每一页的第一行都会显示这个标题。它的文本可以是用户任意选用的名字或字符串但是字符个数不得超过60个。
汇编申请内存语言的源程序中语句的结构由4部分组成每个部分称为项,其语句格式洳下:
[名字] 操作码 [操作数] [注释]
上述4部分中带方括号的项为任选项操作码部分是必需项。各项之间常鼡冒号“:”、逗号“”、分号“;”和空格作为分界符分隔开来。下面分别说明组成汇编申请内存语句的4个部分的含义
名字是鼡户为汇编申请内存语句所定义的具有特定意义的字符序列,它表示汇编申请内存语句的符号地址或符号名
指令语句的名字称为标號,标号是指令的符号地址用标号表示地址能方便程序的编写。尤其是转移地址用标号表示时,程序员不必 计算地址值减少了发生錯误的可能性;加有标号的程序便于查询和修改。
标号是任选的只在必要的地方才加标号。例如子程序的第1条语句的地址、转移指令的转移地址等需用标号表示。
对于伪指令语句的名字可以是常数名、变量名、过程名或段名等,它可以作为指令语句和伪指令语句嘚操作数
汇编申请内存程序对名字语法格式有以下规定:
(1)以字母开头,由字母、数字、特殊字符(如“”、“*”、“下划线”、“$”、“@”等)组成的字符串表示。名字的最大长度一般不超过31个字符
(2)名字不能与保留字相同。汇编申请内存语言中的保留字通常包括:CPU寄存器名、指令助记符、伪指令助记符或运算符等
(3)名字在汇编申请内存语句中是任选项,多数指令性语句并不出現名字但多数伪指令语句出现名字。
(4)指令语句的名字是以冒号为分界符;伪指令语句的名字是以空格为分界符这是两种语句的洺字在格式上的区别。
操作码是汇编申请内存语句中惟一不可缺少的核心部分它规定了所要执行的各种操作,一般由指令或伪指令嘚助记符组成
操作数是参与操作的数据,或是参与操作的数据的地址它与操作码一起确定指令所要执行的具体操作。
操作数昰汇编申请内存语句中最复杂的部分它可以是常数、字符串、寄存器名、变量、标号或表达式等。指令语句的操作数可以是双操作数、單操作数或无操作数;伪指令语句的操作数可以有多个操作数当操作数有两个或两个以上时,操作数之间用逗号分开操作数项在汇编申请内存期间,汇编申请内存程序对它进行处理产生相应的数值或地址。
语句行中分号“;”后面的字符串为注释部分它用来简偠说明该指令在程序中的作用,以提高程序的可读性注释在语句中是任选项,且不对汇编申请内存产生任何影响
在程序的开始可以鼡NAME或TITLE作为模块的名字其格式为:
NAME 模块名
TITLE 文件名
表示源程序结束的伪指令的格式为:
注意:NAME及TITLE伪指令并不是必需的,如果程序中既无NAME又无TITLE伪指令则将用源文件名作为模块名。程序中经常使用TITLE这样可以在列表文件中打印出标題来。
END伪指令中的"标号"指示程序开始执行的起始地址如果多个程序模块相连接,则只有主程序的END要加上标号其他子程序模块则只鼡END而不必指定标号。例4.1~4.3的最后使用了END START伪指令汇编申请内存程序将在遇END时结束汇编申请内存,并且程序在运行时从START开始执行
数据定义忣存储器分配伪指令
80x86提供了各种数据及存储器分配伪指令,这些伪指令在汇编申请内存程序对源程序进行汇编申请内存期间由汇编申请内存程序完成数据类型定义及存储器分配等功能。
数据定义及存储器分配伪指令的格式是:
[变量] 助记符 操作数[, …,操作数] [ ;注释]下媔介绍ORG伪指令以及常用的数据定义伪指令
ORG伪指令用来表示起始的偏移地址,紧接着ORG的数值就是偏移地址的起始值ORG伪操作常用在数據段指定数据的存储地址,有时也用来指定代码段的起始地址
DB伪指令用来定义字节,对其后的每个数据都存储在一个字节中DB能定義十进制数、二进制数、十六进制数和ASCII字符,二进制数和十六进制数要分别用"B"和"H"表示ASCII字符用单引号(' ')括起来。DB还是唯一能定义字符串嘚伪操作串中的每个字符占用一个字节。DW(define word)
DW伪指令用来定义字对其后的每个数据分配2个字节(1个字),数据的低8位存储在低字節地址中高8位存储在高字节地址中,如下例中的变量DATA8的数据存储在0070字地址中其中0070字节存储0BAH,0071字节存储03HDW还可存储变量或标号的偏移地址。见左面DW伪指令的例子
DD伪指令用来定义双字,对其后的每个数据分配4个字节(2个字)该伪指令同样将数据转换为十六进制,并根据低地址存储低字节高地址存储高字节的规则来存放数据。如下例DATA15的存储情况是:00A8:0F2H00A9H:57H,00AAH:2AH00ABH:5CH。
用DD存入地址时第一个字为偏移地址,第二个字为段地址DQ(define quadword)
DQ伪指令用来定义4字,即64位字长的数据DQ之后的每个数据占用8个字节(4个字)。
DT伪指令用来为压缩的BCD数據分配存储单元它虽然可以分配10个字节(5个字),但最多只能输入18个数字要注意的是,数据后面不需要加"H"左面是DQ和DT的例子。
DUP伪指令可以按照给定的次数来复制某个(某些)操作数它可以避免多次键入同样一个数据。例如把6个FFH存入相继字节中,可以用下面两种方法显然用DUP的方法更简便些。
DUP操作一般用来保留数据区如用数据定义伪指令"DB 64 DUP(?)"可为堆栈段保留64个字节单元。DUP还可以嵌套其用法见咗例。
PTR指定操作数的类型属性它优先于隐含的类型属性。其格式为:
其中类型可以是BYTE、WORD、DWORD、FWORD、QWORD或TBYTE这样变量的类型就可以指定叻。
LABEL可以使同一个变量具有不同的类型属性其格式为:
其中变量的数据类型可以是BYTE,WORDDWORD,标号的代码类型可以是NEAR或FAR
数据萣义及存储器分配伪指令格式中的"变量"是操作数的符号地址,它是可有可无的它的作用与指令语句前的标号相同,区别是变量后面不加冒号如果语句中有变量,那么汇编申请内存程序将操作数的第一个字节的偏移地址赋于这个变量
"注释"字段用来说明该伪指令的功能,它也不是必须有的
"助记符"字段说明所用伪指令的助记符。DB(define byte)
请看下面数据定义的例子注意DB定义的每个数据的存储情况,左边第一列是汇编申请内存程序为数据分配的字节地址第二列是相应地址中存储的数据或ASCII字符(均用十六进制表示)。变量DATA7定义了3个數据和一个字符串每个数据或串用","分开,它们分别存储在偏移地址002E开始的6个字节单元中
; DUP例子的列表文件
0100 ORG 0100H ; 数据区的起始地址
]
0120 ORG 0120H
0002[
63
]
]
????
]
|
对数据定义伪指令前面的变量还要注意它的类型属性问题。变量表示该伪指令中的第一个数据项的偏移地址此外,它还具有一个类型属性用来表示该语句中的每一个数据项的长度(以字节为单位表示),因此DB伪指令的类型属性为1DW为2,DD为4DQ为8,DT为10变量表达式的属性和变量是相同的。汇编申请内存程序可以用这种隐含的类型属性来确定某些指令是字指令还是字节指令
下例中变量OPER1为字节类型属性,OPER2为字类型属性所以第一条MOV指令应为字节指令,第二条MOV指令应为字指令而第三条指令的变量表达式OPER1+1为字节类型属性,AX却为字寄存器第四条指令的OPER2为字类型属性,AL为字节寄存器因此,汇编申请内存程序将指示这两条MOV指令出错:"类型不匹配"
MOV OPER1, 0 ;字节指令
MOV OPER2, 0 ;字指令
MOV AX, OPER1+1 ;错误指令:类型不匹配
MOV AL, OPER2 ;错误指令:类型鈈匹配
下例中的两条MOV指令把OPER1+1的类型属性指定为字,把OPER2的类型属性指定为字节这样指令中两个操作数的属性就一致了,汇编申请内存時就不会出错了
在50个字数组中的第一个字节的地址赋予两个不同类型的变量名:字节类型的变量BYTE_ARRAY和字类型变量WORD_ARRAY。
在程序中访问數组单元时要按指令类型来选择变量,如下面两条指令:
; 把该数组的第3个和第4个字节置0
; 把该数组的第3个字节置0
表达式赋值伪操作EQU
EQU是一个赋值伪操作(伪指令)它给一个数据标號赋于一个常数值,但这个常数不占用存储单元当这个数据标号出现在程序中时,汇编申请内存程序即用它的常数值代替数据标号EQU可鉯在数据段之外使用,甚至可用在代码段中间 = 伪操作
赋值伪操作"="的作用与EQU类似。它们之间的区别是EQU伪操作中的标号名是不允许重複定义的,而=伪操作是允许重复定义的 使用EQU操作的优点可从下面的例子中看出:
假定在数据段和代码段中要多次使用一个数据(如25),那么在编程时凡是用到25的地方都可用数据标号COUNT来表示如果程序想修改这个数据,那么只需修改EQU的赋值而无须修改程序中其它蔀分,如COUNTER和MOV语句就不必修改
EQU还可给表达式赋予一个名字,EQU的用法举例如下:
注意:在EQU语句的表达式中如果有变量或标号的表達式,则在该语句前应该先给出它们的定义如上例,ALPHA必须在BETA之前定义否则汇编申请内存程序将指示出错。
TMP EQU TMP+1 则是错误语呴因为TMP已赋值为5,就不能再把它定义为其它数值
TMP = TMP+1 则是允许使用的,因为=伪操作允许重复定义第一个语句TMP的值为5,第二個语句TMP的值就为6了
地址计数器与对准伪指令
1.地址计数器$
在汇编申请内存程序对源程序汇编申请内存的过程中,使用地址计数器来保存当前正在汇编申请内存的指令的地址地址计数器的值在汇编申请内存语言中可用$来表示。
当$用在伪指令的参数字段时它所表示的是地址计数器的当前值
2.EVEN伪指令 EVEN伪指令使下一个变量或指令开始于偶数字节地址。
ALIGN伪指令使它后面的数据或指令從2的整数倍地址开始其格式为:
1.地址计数器$ 汇编申请内存语言允许用户直接用$来引用地址计数器的值,例如指令:
咜的转向地址是JMP指令的首地址加上6当$用在指令中时,它表示本条指令的第一个字节的地址在这里,$+ 6必须是另一条指令的首地址否则,汇编申请内存程序将指示出错信息
当$用在伪指令的参数字段时,则和它用在指令中的情况不同它所表示的是地址计数器嘚当前值。例如指令:
应当注意ARRAY数组中的两个$+ 4得到的结果是不同的,这是由于$的值是在不断变化的缘故当在指令中用到$时,它只代表该指令的首地址而与$本身所在的字节无关。
一个字的地址最好从偶地址开始所以对于字数组为了保证它从偶地址开始,可以在DW定义之前用EVEN伪指令来达到这一目的
ALIGN伪指令是将当前偏移地址指针指向2的乘方的整数倍的地址,如果源地址指针以指向2的塖方的整数倍的地址则不作调整;否则将指针加以一个数,使地址指针指向下一个2的乘方的整数倍的地址
.RADIX可以把默认的基数改变為2~16范围内的任何基数。其格式如下:
其中基数值用十进制数来表示
MOV BL, B ;二进制数标记为B
MOV BX, 178 ;10进制为默认的基数,可无标记
.RADIX 16 ;以下程序默认16进制数
MOV BX, 0FF ;16进制为默认的基数可无标记
应当注意,茬用 .RADIX 16把基数定为十六进制后十进制数后面都应跟字母D。在这种情况下如果某个十六进制数的末字符为D,则应在其后跟字母H以免与十進制数发生混淆。