汇编语言与接口技术第三章 操作指令与寻址方式

汇编语言与接口技术第三章 操作指令与寻址方式

数据类型

介绍操作指令前,先要对汇编中的数据类型有一定了解。

类型可分为字节型数据,字数据,串数据,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在寻址中的作用,它们被统称为间址寄存器。

注意:

  1. 可以放在[]中的寄存器只有间址寄存器四个,一个中括号中出现的间址寄存器不超过2个,并且不能全都是变址寄存器或基址寄存器。

  2. 对于符号地址,[]可以省略,其本身就是一段地址信息,如:

    ...
    DB X 10
    ...
    
    ...
    MOV AX,X 
    MOV AX,[X]
    ;是相互等价的
    
  3. 如果计算出来的有效地址要对64KB取模,换句话说,有效地址只保留十六进制下的低四位

    MOV AX,[BX + 0FFFFH]; 如果BX = 100H,有效地址是 100FFH % (1000H) = 0FFH 
    
  4. 寻址操作不可以用八位寄存器,BL,BH。

  5. 易错辨析 100H[BX]正确,而BX[100H]是错误的

综上,可以总结存储器寻址的 通用公式为EA = DISP + [基址寄存器] + [变址寄存器]

组合形式 名称
DISP 直接寻址
[基址寄存器]或[变址寄存器] 寄存器间接寻址
DISP + [基址寄存器]或[变址寄存器] 相对寄存器寻址
[基址寄存器] + [变址寄存器] 基址变址寻址
DISP + [基址寄存器] + [变址寄存器] 相对基址变址寻址

指令系统

数据传送类指令

MOV

MOV指令可将源操作数的值赋给目的操作数。可以用其实现四个部分的数据交换

  • 段寄存器既可以给通用寄存器赋值也可以接受通用寄存器的数据,对存储器也是同样的,但不能直接由立即数赋值,反之亦然。
  • 通用寄存器可以和段寄存器互通的同时,还可以接受立即数的赋值,并且通用寄存器之间,和存储器之间也是可以相互赋值的
  • 存储器可以和段寄存器和通用寄存器之间赋值,但仅可接受立即数的赋值。
  • 立即数仅仅可以赋值,本身不能改变,且不能直接给段寄存器赋值,需要通过存储器或者通用寄存器的中转。

看图就很好理解:

image

可以发现,除立即数外都是能互相连通的,但只有通用寄存器能够给本类赋值。而立即数不能被赋值,也无法直接对段寄存器赋值。

另外,在使用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指令的一些规范基本也适用大部分的汇编指令规范。

  1. 两操作数不能同时为段寄存器
  2. 两操作数不能同时为存储单元
  3. CS不能做目的操作数,IP不能做操作数
  4. 操作数类型必须匹配
  5. 立即数不能直接修改段寄存器的值
  6. 立即数不能做目的操作数

如有特殊,会有特殊说明。

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 共两步

  1. 栈顶指针SP -= 2
  2. 将源操作数入栈

类似的POP指令也有两步

  1. 将栈中的一个字数据出栈
  2. 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。如果从二进制角度看,我们假设它们都是四位数据,则有

\[(0011)_B - (0101)_B = ((1)0011)_B - (0101)_B = (1110)_B \]

此时出现了向高位的借位。另外因为无符号数是没有负数的,所以用来解决负数情况的补码完全没必要出现。但由于补码本质是求一个二进制数的补数,因此不采用借位的方式而直接转成补码加法也是正确的。

其他情况同理。

对于有符号数

大小 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时,一定要对被除数进行符号位扩展,否则因为高半部分没有设置,导致整体表示数据出错。

除法指令在编程时要小心使用,它非常容易导致程序出错。常见错误有零中断和商溢出

  1. 零中断:除数为0,会导致程序卡死。
  2. 商溢出:商过大而超过了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的变化。判断标准是符号位是否改变。

可供参考的优质文章还有:

常用汇编指令对标志位的影响 - kk Blog —— 通用基础 (abcdxyzk.github.io)

汇编语言算术运算指令 | 程序员忆初 (developerastrid.com)

posted @ 2022-02-14 11:36  Mxrurush  阅读(479)  评论(1编辑  收藏  举报