汇编语言与接口技术第三章 操作指令与寻址方式
汇编语言与接口技术第三章 操作指令与寻址方式
数据类型
介绍操作指令前,先要对汇编中的数据类型有一定了解。
类型可分为字节型数据,字数据,串数据,ASCII码,BCD码和指针数据。
需要注意的是虽然有对数据类型的划分,但是在计算机内部都是以二进制编码的方式储存。为了对不同数据类型进行正确的处理,需要用特定的伪指令告诉计算机如何汇编程序。
寻址方式
寻址方式是考试的重中之重,分为立即数寻址,寄存器寻址和存储器寻址三大类,存储器寻址又可以进一步细分,最后共7种要求掌握。
立即数寻址
直接以编写好的常数作为源操作数的寻址方式,速度快效率高,但因为立即数已经在编程时被定死所以通用性差。
因此立即数寻址一般的作用是给寄存器赋初值。
MOV AX,0200H ; 0200H -> AX
注意:立即数只出现在指令中,并且立即数不能作为目的操作数(因为我们不能给一个常数再赋值,并且即使能赋值,也无法存起来)。
寄存器寻址
提取寄存器中保存数据的存储方式。因为不用访问存储器,效率仍然很快。
使用时直接写寄存器对应的名字,得到其中数据。
MOV AX,BX ; 把BX中数据放入AX
MOV AX,0200H ; 对源操作数是立即数寻址,对目的操作数又是寄存器寻址。
存储器寻址
访问存储器的寻址方式,因为在和存储器交互中,CPU将一段逻辑地址传给存储器,所以根据对逻辑地址表示的不同,又细分为五类
直接寻址
通过一个常数作为偏移地址和默认段首址构成逻辑地址。
MOV AX,[1000H]; 将DS:1000中的数据赋给AX
形式上和立即数寻址的差别只在给常数加了一个[],在汇编语言中,加方括号表示要将里面的数据当成地址数据处理。
PS:一般默认段首址是DS。
寄存器间接寻址
把寄存器中数据作为偏移地址。注意和寄存器寻址进行区分
MOV AX,[BX]; 设BX = 1000H,意思就是把DS:1000H 的数据赋给AX
相对寄存器寻址
偏移地址由寄存器中数据和立即数构成。
MOV AX,[BX + 2]; 设BX = 1000H,则意思就是把DS:1002H 的数据赋给AX
; 等价的表达还有
MOV AX,[BX][2]
MOV AX.[2][BX]
基址变址寻址
由基址寄存器(BP,BX),变址寄存器(SI,DI)构成偏移地址
MOV AX,[BX + SI]; 设BX = 1000H,SI = 2,则意思就是把DS:1002H 的数据赋给AX
;等价表达还有
MOV AX,[BX][SI]
MOV AX,[SI][BX]
如果基址寄存器是BX,默认的段首址是DS,如果是BP,默认基址是SS
相对基址变址寻址
就是上面相对寄存器寻址和基址变址寻址的结合
MOV AX,[BX + SI + 2]
; 等价表述
MOV AX,[BX][SI][2]
MOV AX,[SI][BX][2]; 其他同理
因为BX,BP,SI,DI在寻址中的作用,它们被统称为间址寄存器。
注意:
-
可以放在[]中的寄存器只有间址寄存器四个,一个中括号中出现的间址寄存器不超过2个,并且不能全都是变址寄存器或基址寄存器。
-
对于符号地址,[]可以省略,其本身就是一段地址信息,如:
... DB X 10 ... ... MOV AX,X MOV AX,[X] ;是相互等价的
-
如果计算出来的有效地址要对64KB取模,换句话说,有效地址只保留十六进制下的低四位
MOV AX,[BX + 0FFFFH]; 如果BX = 100H,有效地址是 100FFH % (1000H) = 0FFH
-
寻址操作不可以用八位寄存器,BL,BH。
-
易错辨析 100H[BX]正确,而BX[100H]是错误的
综上,可以总结存储器寻址的 通用公式为EA = DISP + [基址寄存器] + [变址寄存器]
组合形式 | 名称 |
---|---|
DISP | 直接寻址 |
[基址寄存器]或[变址寄存器] | 寄存器间接寻址 |
DISP + [基址寄存器]或[变址寄存器] | 相对寄存器寻址 |
[基址寄存器] + [变址寄存器] | 基址变址寻址 |
DISP + [基址寄存器] + [变址寄存器] | 相对基址变址寻址 |
指令系统
数据传送类指令
MOV
MOV指令可将源操作数的值赋给目的操作数。可以用其实现四个部分的数据交换
- 段寄存器既可以给通用寄存器赋值也可以接受通用寄存器的数据,对存储器也是同样的,但不能直接由立即数赋值,反之亦然。
- 通用寄存器可以和段寄存器互通的同时,还可以接受立即数的赋值,并且通用寄存器之间,和存储器之间也是可以相互赋值的
- 存储器可以和段寄存器和通用寄存器之间赋值,但仅可接受立即数的赋值。
- 立即数仅仅可以赋值,本身不能改变,且不能直接给段寄存器赋值,需要通过存储器或者通用寄存器的中转。
看图就很好理解:
可以发现,除立即数外都是能互相连通的,但只有通用寄存器能够给本类赋值。而立即数不能被赋值,也无法直接对段寄存器赋值。
另外,在使用MOV时,要时刻注意保证目的操作数和源操作数的数据类型一致(不能一个8位,一个16位)
对考试来说有这么一些经常出现的辨析
MOV DS,SS; 段寄存器之间赋值 错
MOV CS,AX; CS不能做目的操作数
MOV AX,IP ; IP不能做操作数 错
MOV [AX],[BX]; 存储器中数据直接赋值 错
MOV 10H,AX; 给立即数赋值 错
MOV DS,100H;立即数直接给段寄存器赋值 错
MOV AL,0FFFFH; AL是八位寄存器 立即数是16位的 类型不匹配 错
MOV [BX],1; 两边的类型指向不明确 错 PS: 有的masm版本会做自动推导,也是有可能运行成功的
MOV CX,[AX]
MOV AX,[SI][DI]
MOV AX,[100H]BX
; 有效地址格式错误 错
总之判断MOV指令正确与否,在考试中看是否符号寻址规范和上图即可。
MOV指令的一些规范基本也适用大部分的汇编指令规范。
- 两操作数不能同时为段寄存器
- 两操作数不能同时为存储单元
- CS不能做目的操作数,IP不能做操作数
- 操作数类型必须匹配
- 立即数不能直接修改段寄存器的值
- 立即数不能做目的操作数
如有特殊,会有特殊说明。
LEA
LEA可以将原操作数的偏移地址传给目的操作数。因为偏移地址默认的是一个字数据,所以只目的操作数只接受十六位寄存器,源操作数只能采用存储器寻址,是一个MEM。
LEA主要做好和MOV指令的区分。
LEA AX,[BX + SI]; 得到源操作数的有效地址,本例中就是BX + SI的值
MOV AX,[BX + SI]; 得到DS:[BX + SI]这个地址下存储单元中的值
当源操作数是变量或标号时,MOV有对LEA的等价写法
MOV AX,OFFSET X
LEA AX,X
它们之间是等价的,AX都被赋值为变量X的有效地址,但是它们的源操作数寻址方式是不同的。
对前者来说,\(OFFSET\) 是一条伪指令,它会使得系统在汇编链接过程中求X的有效地址,所以如果假设X的EA = 1000H,在运行过程中:MOV AX,OFFSET X 实际上被转成了MOV AX ,1000H 。而后者则是在程序运行过程中求X的偏移量。
因此,OFFSET X 的寻址方式是立即数寻址,而后者是直接寻址。
这也可以解释为什么 MOV AX,OFFSET [BX + SI] 是错误的,因为\(OFFSET\) 是在程序运行前已经处理好目的操作数的偏移地址,但BX和SI等在程序开始前还没被赋值所以不行,而标号变量已经被分配了地址相当于常量,所以可以。
PUSH和POP
都是堆栈指令,分别代表入栈和出栈。POP是PUSH的逆操作,对堆栈操作做一个比喻的话,就像是”水涨船高“,SP是船,数据是水。
PUSH 共两步
- 栈顶指针SP -= 2
- 将源操作数入栈
类似的POP指令也有两步
- 将栈中的一个字数据出栈
- SP += 2
注意加减号和操作顺序,栈底地址最大,栈顶地址最小。并且它们只接受16位的操作数。
另外16位机不接受立即数做显式操作数。
XCHG
交换两操作数的值。除所有指令都要注意的几点还要注意XCHG不接受立即数作为操作数。
CBW和CWD
符号位扩展指令,将AL或AX的符号位扩展到AH或DX中。作用是数位对齐,扩展原则见我的这篇博客。
另外它们都是针对AX或者DX:AX进行扩展的,没有显式操作数。
逻辑计算指令
加法
进行加法有ADD的半加和,ADC的全加,作用都是把两操作数相加结果放在目的操作数中。区别是CF是否参与运算
INC 自增1,也就是高级语言中的 ++。注意INC操作数不会影响CF。
减法
和加法完全一致,同样有三个操作指令SUB,SBB,DEC。其中DEC表示自减1
减法独特的运算符DEG,CMP,它们都是基于减法实现。
DEG是取补指令,效果是得到操作数的补数,可以说将原数字包括符号位取反再加一也可以说是把0 - OPRD的值赋给OPRD(OPRD是操作数),这两个是等价表述,所以DEG就是得到当前数字的相反数的操作。这个操作可以快速的得到一个数对应相反数的补码。
CMP是比较指令,它将两操作数作差仅改变符号位不改变值,通常和各种跳转指令一块使用构成汇编程序中的if-else语句。CMP的判断原理如下
如果将两操作数看成无符号数。
大小 | ZF | CF |
---|---|---|
OPRD1<OPRD2 | 0 | 1 |
OPRD1=OPRD2 | 1 | 0 |
OPRD1>OPRD2 | 0 | 0 |
看小于的情况,如果设这两个无符号数分别是\(a=3,b=5\),执行CMP指令时,就有\(a - b = -2\),由于是无符号数,实际上是\(-2\)对\(2^{n + 1}\),的补数,如果从十进制角度看,此时-2已经超范围了,所以CF = 1。如果从二进制角度看,我们假设它们都是四位数据,则有
此时出现了向高位的借位。另外因为无符号数是没有负数的,所以用来解决负数情况的补码完全没必要出现。但由于补码本质是求一个二进制数的补数,因此不采用借位的方式而直接转成补码加法也是正确的。
其他情况同理。
对于有符号数
大小 | FLAG |
---|---|
OPRD1<OPRD2 | SF != OF |
OPRD1=OPRD2 | SF = OF |
OPRD1>OPRD2 | ZF = 1 |
乘法
分为有符号乘法IMUL和无符号乘法MUL两种。作用是把AL(或AX)中的数据和源操作数数据做乘法之后将结果置于AX(或DS:AX)中。操作的位数根据源操作数的位数决定。
乘法指令对标志位的影响:
运用乘法指令时只需要考虑CF和OF。其他标志位不做考虑。
对无符号乘法MUL,当结果的高半部分全为0(AH或DX,根据操作数位数决定)时OF,CF置零,否则都置1。
对有符号乘IMUL,当高半部分是符号位扩展时CF,OF置零,反之置1.
除法
除法和乘法类似,有DIV无符号除和IDIV有符号除法两种。但由于有余数的存在,会将商置于AL(或者AX)中,将余数置于AH(或DX)中。
注意
在使用IDIV时,一定要对被除数进行符号位扩展,否则因为高半部分没有设置,导致整体表示数据出错。
除法指令在编程时要小心使用,它非常容易导致程序出错。常见错误有零中断和商溢出。
- 零中断:除数为0,会导致程序卡死。
- 商溢出:商过大而超过了8位或者16位,会导致答案错误。
另外余数的符号位总和被除数一致。
除法指令对标志位的影响:
所有标志位均未定义,不用分析除法对符号位的 影响。
位运算指令
~&|^
重点掌握对符号位的影响
- NOT: 不改变符号位
- AND,OR,XOR,TEST:CF,OF置零,其余正常根据结果判断。
TEST一般用于判断一个数的某几位是否为1或0
移位指令
逻辑移位指令
共两个,SHL,SHR。相当于无符号数的除2乘2,将高位移入CF(或补零),之后将低位补零(或放入CF)。
算数移位指令
SAL,SAR。相当于有符号数的除2和乘2,左移将高位移入CF,末位补零。右移符号位不变,其余右移,末尾放入CF。
循环移位指令
SOL,SOR。整体循环,左移将高位放入CF,右移将低位放入CF。
带进位的循环移位
SCL,SCR。左移就将CF当最高位的循环移位,右移就将CF当最低位的循环移位。
移位指令对标志位的影响
CF由操作过程决定。
如果是逻辑或者算数移位SF,ZF由结果决定。如果是循环移位,SF,ZF不受影响。
当移位次数大于1时,才考虑OF的变化。判断标准是符号位是否改变。