两个基本问题

  1. 处理的数据在什么地方
  2. 要处理的数据有多长

接下来的讨论中,使用reg来表示一个寄存器,使用sreg来表示一个段寄存器。所以:

  • reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
  • sreg:ds,ss,cs,es

    bx,si,di和bp

    注意:
    在8086CPU中,只有这四个寄存器可以使用[……]来进行内存寻址,可以单个出现,或以下面组合出现(常数可以随意出现在这些表示方法中)

  • bx+si/di

  • bp+si/di

比如下列指令是错误的
mov ax,[bx+bp]
mov ax,[si+di]

注:如果使用了bp来寻址,而没有显式的表明段地址,默认使用ss段寄存器,如:

  1. mov ax,[bp] ;(ax)=((ss)*16+(bp))
  2. mov ax,[bp+idata] ;(ax)=((ss)*16+(bp)+idata)
  3. 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,所以在使用数据的时候要指明是字操作还是字节操作。

  • 在有寄存器参与的时候使用寄存器的种类区分

    1. ;下面的指令中,寄存器指明了指令进行的是字操作
    2. mov ax,1
    3. mov bx,ds:[0]
    4. mov ds,ax
    5. mov ds:[0],ax
    6. inc ax
    7. add ax,1000
    8. ;下面的指令中,寄存器指明了指令进行的是字节操作
    9. mov al,1
    10. mov al,bl
    11. mov al,ds:[0]
    12. mov ds:[0],al
    13. inc al
    14. 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’
  1. ···
  2. mov ax,seg
  3. mov ds,ax
  4. mov bx,60h
  5. mov word ptr [bx].0ch,38 ;第三字段改为38
  6. add word ptr [bx].0eh,70 ;第四字段改为70
  7. mov si,0
  8. mov byte ptr [bx].10h[si],'v' ;修改最后一个字段的三个字符
  9. inc si
  10. mov byte ptr [bx].10h[si],'A'
  11. inc si
  12. mov byte ptr [bx].10h[si],'X'
  13. ···

这段代码中地址的使用类似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 regdiv 内存单元,所以
div byte ptr ds:[0]表示:

  1. (al)=(ax)/((ds)*16+0)的商;
  2. (ah)=(ax)/((ds)*16+0)的余数;

div word ptr es:[0]表示:

  1. (al)=[(dx)*10000H+(ax)]/((es)*16+0)的商
  2. (ah)=[(dx)*10000H+(ax)]/((es)*16+0)的余数

例:计算100001/100,因为100001(186A1H)大于65535,则需要存放在ax和dx两个寄存器,那么除数100只能存放在一个16位的寄存器中,实现代码:

  1. mov dx,1
  2. mov ax,86A1H
  3. mov bx,100
  4. div bx

执行之后(ax)=03E8H(1000),(dx)=1。

伪指令dd

dd是一个伪指令,类似dw,但dd是用来定义dword(double word,双字),如:

  1. dd 1 ;2字,4字节
  2. dw 1 ;1字,2字节
  3. db 1 ;1字节

将data段中第一个数据除以第二个数据,商存入第三个数据:

  1. ···
  2. data segment
  3. dd 100001
  4. dw 100
  5. dw 0
  6. data ends
  7. ···
  8. mov ax,data
  9. mov ds,ax
  10. mov ax,ds:[0]
  11. mov dx,ds:[2]
  12. div word ptr ds:[4]
  13. mov ds:[6],ax
  14. ···

总结一下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 (重复内容)