两个基本问题
- 处理的数据在什么地方
- 要处理的数据有多长
接下来的讨论中,使用reg来表示一个寄存器,使用sreg来表示一个段寄存器。所以:
- reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
-
bx,si,di和bp
注意:
在8086CPU中,只有这四个寄存器可以使用[……]来进行内存寻址,可以单个出现,或以下面组合出现(常数可以随意出现在这些表示方法中): bx+si/di
bp+si/di
比如下列指令是错误的
mov ax,[bx+bp]
mov ax,[si+di]
注:如果使用了bp来寻址,而没有显式的表明段地址,默认使用ss段寄存器,如:
mov ax,[bp] ;(ax)=((ss)*16+(bp))
mov ax,[bp+idata] ;(ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si] ;(ax)=((ss)*16+(bp)+(si)+idata)
数据的位置
绝大部分机器指令都是用来处理数据的,基本可分为读取,写入,运算。在机器指令这个层面上,并不关心数据是什么,而关心指令执行前数据的位置。一般数据会在三个地方,CPU内部,内存,端口。
汇编语言中使用三个概念来表示数据的位置:
- 立即数(idata)
- 对于直接包含在机器指令中的数据,在汇编语言中称为立即数
- 例:
mov ax,1
add bx,2000h
- 寄存器
- 指令要处理的数据在寄存器中,在汇编指令中给出相应寄存器名
- 例:
mov ax,bx
mov ds,ax
- 段地址(SA)和偏移地址(EA)
- 指令要处理的数据在内存中,在指令中使用[X]方式给出,SA在某个段寄存器中
- 例:
mov ax,[0]
mov ax,[di]
总结一下寻址方式:
寻址方式 | 含义 | 名称 |
---|---|---|
[idata] | EA=idata;SA=(DS) | 直接寻址 |
[bx|si|di|bp] | EA=(bx|si|di|bp);SA=(DS) | 寄存器间接寻址 |
[bx|si|di|bp+idata] | EA=(bx|si|di|bp+idata);SA=(DS) | 寄存器相对寻址 |
[bx|bp+si|di] | EA=(bx|bp+si|di);SA=(DS|SS) | 基址变址寻址 |
[bx|bp+si|di+idata] | EA=(bx|bp+si|di+idata);SA=(DS|SS) | 相对基址变址寻址 |
数据的长度
8086CPU中可以指定两种尺寸的数据,byte和word,所以在使用数据的时候要指明是字操作还是字节操作。
在有寄存器参与的时候使用寄存器的种类区分
;下面的指令中,寄存器指明了指令进行的是字操作
mov ax,1
mov bx,ds:[0]
mov ds,ax
mov ds:[0],ax
inc ax
add ax,1000
;下面的指令中,寄存器指明了指令进行的是字节操作
mov al,1
mov al,bl
mov al,ds:[0]
mov ds:[0],al
inc al
add dl,100
来源和目的地的寄存器长度需要一样,不然会报错。如果有数据源是内存地址或者常量,会根据寄存器的宽度来扩展。如果常量的长度超过寄存器宽度,也会报错。
在没有寄存器参与的时候,使用操作符X ptr指明内存单元长度,X是word或byte
- 字:
mov word ptr ds:[0],1
add word ptr [bx],2
- 字节:
mov byte ptr ds:[0],1
add byte ptr [bx],2
- 字:
其他默认指明处理类型的指令
push [1000H]
,push/pop默认只进行字操作
灵活使用寻址方式的例子,修改下面内存空间中的数据:
段seg:60
起始地址 | 内容 |
---|---|
00 | ‘DEC’ |
03 | ‘Ken Oslen’ |
0C | 137 |
0E | 40 |
10 | ‘PDP’ |
···
mov ax,seg
mov ds,ax
mov bx,60h
mov word ptr [bx].0ch,38 ;第三字段改为38
add word ptr [bx].0eh,70 ;第四字段改为70
mov si,0
mov byte ptr [bx].10h[si],'v' ;修改最后一个字段的三个字符
inc si
mov byte ptr [bx].10h[si],'A'
inc si
mov byte ptr [bx].10h[si],'X'
···
这段代码中地址的使用类似c++中结构体的使用。[bx].idata.[si],就类似与c++中的dec.cp[i]。dec是结构体,cp是结构体中的字符串成员,[i]表示第几个字符。
div指令
div是除法指令,需要注意以下三点:
- 除数:8位或16位,在一个reg或内存单元中
- 被除数:默认在AX或DX中,如果除数8位,被除数则为16位,放在AX中;如果除数16位,则被除数32位,在DX和AX中,DX存放高16位,AX放低16位。
- 结果,除数8位,结果(商)存放在AL中,AH存放余数;如果除数16位,则AX存放商,DX存放余数
格式:div reg
或div 内存单元
,所以div byte ptr ds:[0]
表示:
(al)=(ax)/((ds)*16+0)的商;
(ah)=(ax)/((ds)*16+0)的余数;
div word ptr es:[0]
表示:
(al)=[(dx)*10000H+(ax)]/((es)*16+0)的商
(ah)=[(dx)*10000H+(ax)]/((es)*16+0)的余数
例:计算100001/100,因为100001(186A1H)大于65535,则需要存放在ax和dx两个寄存器,那么除数100只能存放在一个16位的寄存器中,实现代码:
mov dx,1
mov ax,86A1H
mov bx,100
div bx
伪指令dd
dd是一个伪指令,类似dw,但dd是用来定义dword(double word,双字),如:
dd 1 ;2字,4字节
dw 1 ;1字,2字节
db 1 ;1字节
将data段中第一个数据除以第二个数据,商存入第三个数据:
···
data segment
dd 100001
dw 100
dw 0
data ends
···
mov ax,data
mov ds,ax
mov ax,ds:[0]
mov dx,ds:[2]
div word ptr ds:[4]
mov ds:[6],ax
···
总结一下div相关:
- div后面跟的是除数
- 被除数位数是除数两倍
- 被除数存在ax中或ax+dx(ax低,dx高)
- 商在ax或al中,余数在ah或dx中(高余数,低商)
dup
dup是一个操作符,由编译器识别,和db,dw,dd配合使用,如:
db 3 dup (0)表示定义了三个值是0的字节,等价于db 0,0,0
db 3 dup (1,2,3)等价于db 1,2,3,1,2,3,1,2,3 共九个字节
db 3 dup (‘abc’,‘ABC’)等价于db ‘abcABCabcABCabcABC’
综上,db|dw|dd 重复次数 dup (重复内容)