数据传送、寻址和算术运算
4.1 数据传送指令
4.1.1 简介
第一段一大推,就是为了说明汇编相比高级语言来说比较麻烦需要注意很多细节,但是换来的好处是给开发者带来了更大的灵活性。
读者如果肯花时间彻底掌握本章内容,那么本书的后续部分将更容易学习。对以后变得越来越复杂的例子程序的理解,将在很大程度上依赖于对本章提供的基本工具的掌握。
4.1.2 操作数类型
本章讲述三种类型的操作数:立即操作数(immediate)、寄存器操作数(register)和内存操作数(memory)。在这三者当中,只有内存操作数稍微有点复杂。下表列出的操作数的简写符号是从Intel IA-32手册上摘录下来的。
4.1.3 直接内存操作数
3.4节已经解释过,变量名仅仅是对数据段内偏移地址的引用。下面的声明表示一个包含数字10h的字节被置于数据段内:
.data
var1 BYTE 10h
指令使用内存操作数时实际上使用的书操作数的地址。假设var1位于偏移10400h处,那么把var1送AL寄存器的汇编指令如下:
mov AL,var1
MASM将这条指令汇编成如下的机器指令:
A0 00010400
机器指令的第一个字节是操作码,剩下的部分是变量var1的十六进制的32位地址值。编写程序时仅使用纯数字地址表示内存操作是可以的,不过像var1这样的符号名使得引用内存时更加方便一些。
一些开发者更喜欢使用下面的方式表示直接操作数,因为方括号按时了要进行寻址操作:
mov al,[var1]
MASM允许使用这种方式。mov al,[var1+5] (这称为直接偏移操作数。)
4.1.4 MOV指令
MOV指令从源操作数向目的操作数复制数据。作为数据传送指令,几乎每个程序中都会用到MOV。其基本格式是:第一个操作数是目的(destination)操作数,第二个操作数是源(source)操作数:
MOV destination,source
指令运行后,目的操作数的内容被改变而源操作数的内容保持不变。
MOV指令对操作数的使用是非常灵活的,只要遵循以下规则即可:
1.两个操作数的尺寸必须一致。
2.两个操作数不能同时为内存操作数。
3.目的操作数不能是CS,EIP和IP。
4.立即数不能直接送至段寄存器。
下面是MOV指令格式列表,但寄存器(reg)是不包括段寄存器的:
MOV reg ,reg
MOV mem ,reg
MOV reg ,mem
MOV mem ,imm
MOV reg ,imm
在运行于保护模式下时,程序不应直接修改段寄存器。一般来说,段寄存器仅应由实地址模式下运行的程序使用,对段寄存器的操作可以有一下两种格式,唯一的例外是CS不能用作目的操作数:
MOV r/m16 ,sreg
MOV sreg ,r/m16
内存之间的移动:单条MOV指令不能把数据从一个内存位置直接移动到另外一个内存位置。
作为一种替代方法,在送至目的操作数之前,可以先把源操作数移入一个寄存器中:
.data
var1 WORD ?
var2 WORD ?
.code
mov ax ,var1
mov var2,ax
4.1.5 整数的零/符号扩展
复制较小值至较大值中
尽管不能直接使用MOV指令把数据从一个尺寸较小的操作复制到一个尺寸较大的操作数中,但有时确实需要这样的移动数据。例如,假设字count(无符号16位数)必须送到ECX(32位)中,一个简单的解决方法是先把ECX清零,然后再把count送到CX中:
.data
count WORD 1
.code
mov ecx ,0
mov cx,count
但是注意一个问题,就是操作负数的时候会有问题。如:
.data
signedVal SWORD -1
.code
mov ecx ,0
mov cx,signedVal
这种情况,我们可以以FFFFFFFFh填充ECX,然后复制signedVal至CX中,那么最终结果将是正确的:
.data
signedVal SWORD -1
signedVa2 SDWORD 0
.code
mov ecx ,0FFFFFFFFh
mov cx,signedVal
mov signedVa2,ecx
这样虽然可以解决问题,但是比较麻烦,所以引入了MOVZX和MOVSX指令,已处理有符号和无符号整数。
MOVZX指令
MOVZX(move with zero-extend,零扩展传送)指令将源操作数的内容复制到目的操作数中,并将该值扩展(zero-extend)至16位或32位。该指令仅适用于无符号整数,它有如下三种格式:
Movzx r32 ,r/m8
Movzx r32,r/m16
Movzx r16,r/m8
在此三种格式中第一个操作数是目的操作数而第二个操作数是源操作数,目的操作数必须是寄存器。下面的指令把二进制10001111送AX:
mov b1 ,10001111b
movzx ax,bl
下图解释了8位源操作数是如何扩展成16位目的操作数的:
下面的例子中所有的操作数全部使用寄存器,演示了所有可能的尺寸格式组合:
下面的例子使用内存操作数作为源操作数,产生的结果同上:
MOVSX指令
和movzx指令类似,但是它是处理有符号整数的。
4.1.6 LAHF和SAHF指令
LAHF(load status flags into AH)指令将EFLAGS寄存器的低字节复制到AH寄存器,被复制的标志包括:符号标志、零标志、辅助进位标志、奇偶标志和进位标志。使用该指令可以方便地将标志值保存在变量中:
.data
saveflags BYTE 0
b1 BYTE 0
.code
LAHF
mov saveflags ,ah
SAHF(store AH into status flags)指令复制AH寄存器的值至EFLAGS寄存器的低字节,例如,可以用如下指令回复刚才保存在变量中的标志:
mov ah ,saveflags
SAHF
4.1.7 XCHG指令
XCHG(exchange data)指令交换两个操作数的内容,它有下面三种格式:
XCHG reg,reg
XCHG reg,mem
XCHG mem,reg
XCHG指令不接受立即数操作数,除此点不同之外,XCHG指令的操作数与MOV指令的操作数遵循同样的规则。在用到数组排序的应用程序中,XCHG指令提供了交换连个数组元素的简便方法,以下是一些使用XCHG指令的例子:
xchg ax,bx ;交换两个16位寄存器的内同
xchg ah,al ;交换两个8位寄存器的内容
xchg var1,bx;交换16位的内存操作数和BX寄存器内容
xchg eax,ebx;交换两个32位寄存器的内同
若要交换两个内存操作数,需要使用一个寄存器作为临时存储容器,并把mov指令和xchg指令结合起来使用:
mov ax ,val1
xchg ax ,val2
mov val1 ,ax
4.1.8 直接偏移操作数
在变量名称后加上一个偏移值,可以创建直接偏移(direct-offset)操作数,可以通过它来访问没有显示标号的内存地址。我们以一个名为arrayB的字节数组开始枚举:
.data
arrayB BYTE 10h ,20h ,30h ,40h ,50h
.code
mov al ,arrayB
mov al ,[arrayB+1]
mov al ,[arrayB+2]
如果是双字节或者是其他,注意偏移的时候地址是2或者其他等,比较简单不细说了。
范围检查:MASM不对有效地址进行范围检查,对下面的语句,汇编器将原样翻译。如果执行下面的语句,就能够取得数组之外的一个内存字节。这可能会造成一个非常隐蔽的逻辑错误,因此开发者在检查对数组的引用时应该格外小心
4.1.9 例子程序(数据传送)
算是对4.1的一个总结
TITLE Data Transfer Examples (Moves.asm)
INCLUDE Irvine32.inc
.data
val1 WORD 1000h
val2 WORD 2000h
arrayB BYTE 10h ,20h ,30h ,40h ,50h
arrayW WORD 100h ,200h ,300h
arrayD DWORD 10000h ,20000h
.code
main PROC
;MOVZX
mov bx ,0A65Bh
movzx eax,bx ;EAX = 0000A64Bh
movzx cx ,bl ;CX = 009Bh
;MOVSX
mov bx ,0A69Bh
movsx eax,bx ;EAX = FFFFA69Bh
movsx edx,bl ;EDX = FFFFFF9Bh
mov bl,7Bh
movsx cx,bl ;CX = 007Bh
;内存到内存的交换
mov ax ,val1 ;AX = 1000h
xchg ax ,val2 ;AX=2000h ,val2=1000h
mov val1 ,ax ;val1 = 2000h
;直接偏移寻址(字节数组)
mov al ,arrayB ;AL = 10h
mov al ,[arrayB+1] ;AL = 20h
mov al ,[arrayB+2] ;AL = 30h
;直接偏移寻址(字数组)
mov ax ,arrayW ;AX = 100h
mov ax ,[arrayW+2] ;AX = 200h
exit
main ENDP
END main
4.2 加法和减法
本章着重讲述整数的加法和减法;第7张讲述整数的乘法和出发;第17章介绍如何进行浮点运算,使用另外一套与整数运算完全不同指令。
4.2.1 INC和EDC指令
INC(increment)和DEC(decrement)指令从操作数中加1或减1,格式是:
INC reg/mem
DEC reg/mem
下面是一些例子:
.data
myWord WORD 1000h
.code
Inc myWord ;4097 并不是1001h 书上写错了
mov bx ,myWord
dec bx ;1000h
4.2.2 ADD指令
ADD指令将同尺寸的源操作数和目的操作数相加,格式是:
ADD 目的操作数,源操作数,例子:
.data
var1 DWORD 10000h
var2 DWORD 20000h
.code
mov eax ,var1 ;EAX = 10000h
add eax ,var2 ;EAX = 30000h
4.2.3 SUB指令
SUB指令将源操作数从目的操作数中减掉,操作数格式与ADD和MOV指令操作数相同。
SUB 目的操作数 ,源操作数
例子:
.data
var1 DWORD 30000h
var2 DWORD 10000h
.code
mov eax ,var1 ;EAX = 30000h
sub eax ,var2 ;EAX = 20000h
有一种执行减法而无需使用额外的数字电路单元的简单方法:对源操作数求补,然后把源才做书和目的操作数相加。例如4-1可以看做是 4 + (-1)
影响的标志:SUB指令根据目标操作数的值相应的修改进位标志、零标志。符号标志、溢出标志、辅助进位标志和奇偶标志。
4.2.4 NEG指令
NEG(negate)指令通过将数字转换为对应的补码而求得其相反数:
NEG reg
NEG mem
4.2.5 实现算术表达式
Rval = -Xval + (Yval - Zval);
汇编实现
.data
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
.code
mov eax ,Xval
neg eax
mov ebx ,Yval
sub ebx ,Zval
add eax ,ebx
mov Rval ,eax
4.2.6 加法和减法影响的标志位
略
4.2.7 例子程序(AddSub3)
TITLE Addition and Subtraction (AddSub3.asm)
INCLUDE Irvine32.inc
.data
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
.code
main PROC
;INC and DEC
mov ax ,1000h
inc ax ;1000h + 1
dec ax ;1000h
;Rval = -Xval + (Yval - Zval)
mov eax ,Xval
neg eax
mov ebx ,Yval
sub ebx ,Zval
add eax ,ebx
mov Rval ,eax
;零标志的例子
mov cx ,1
sub cx ,1 ;ZF = 1
mov ax ,0FFFFh
inc ax ;ZF = 1
;符号标志的例子
mov cx ,0
sub cx ,1 ;SF = 1
mov ax ,7FFFh
add ax ,2 ;SF = 1
;进位标志的例子
mov al ,0FFh
add al ,1 ;OF = 1
mov al ,-128
sub al ,1 ;OF = 1
exit
main ENDP
END main