第八章 数据处理的两个基本问题

引言

本章对前面的所有内容是具有总结性的。我们知道,计算机是进行数据处理、运算的机器,那么有两个基本的问题就包含在其中:

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

这两个问题,在机器指令中必需给以明确或隐含的说明,否则计算机就无法工作。本章中,我们就要针对8086CPU对这两个基本问题进行讨论。虽然讨论是在8086CPU的基础上进行的,但是这两个基本问题却是普遍的,对任何一个处理器都存在。

我们定义的描述性符号:regsreg 。即,寄存器(register)和段寄存器(segment register)。

为了描述上的简洁,在以后的课程中,我们将使用两个描述性的符号 reg 来表示一个寄存器,用 sreg 表示一个段寄存器。

  • reg 的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
  • sreg 的集合包括:ds、ss、cs、es。

8.1 bx、si、di、bp

前三个寄存器我们已经用过了,现在我们进行一下总结。

  1. 在8086CPU中,只有这4个寄存器(bx、bp、si、di)可以用在“[…]”中来进行内存单元的寻址。

    以下我们来区分好正确和错误的用法。

    正确的指令:

    mov ax,[bx]
    mov ax,[bx+si]
    mov ax,[bx+di]
    mov ax,[bp]
    mov ax,[bp+si]
    mov ax,[bp+di]

    错误的指令:

    mov ax,[cx]
    mov ax,[ax]
    mov ax,[dx]
    mov ax,[ds]
  2. 在“[…]”中,这4个寄存器(bx、bp、si、di)可以单个出现,或只能以四种组合出现:

    • bx 和 si
    • bx 和 di
    • bp 和 si
    • bp 和 di

    错误的用法(bx 和 bp 不能组合;si 和 di 不能组合):

    mov ax,[bx+bp]
    mov ax,[si+di]

    正确的用法:

    mov ax,[bx]
    mov ax,[si]
    mov ax,[di]
    mov ax,[bp]
    mov ax,[bp]
    mov ax,[bx+si]
    mov ax,[bx+di]
    mov ax,[bp+si]
    mov ax,[bp+di]
    mov ax,[bx+si+idata]
    mov ax,[bx+di+idata]
    mov ax,[bp+si+idata]
    mov ax,[bp+di+idata]
  3. 只要在 [...] 中使用寄存器 bp,而指令中没有显性的给出段地址,段地址就默认在 ss 中(从这里就可以看出 bp 是帮忙 sp 来减轻负担的)。比如:

    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))
    mov ax,[bp+si+idata] ;(ax) = ((ss)*16+(bp)+(si)+idata)

8.2 机器指令处理的数据所在位置

绝大部分机器指令都是进行数据处理的指令,处理大致可分为三类:读取、写入、运算。

在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令在执行前,所要处理的数据可以在三个地方:

  • CPU内部
  • 内存
  • 端口(端口我们将在后面的课程中进行讨论)

举例:

机器码 汇编指令 指令执行前数据的位置
8E1E0000 mov bx,[0] 内存,ds:0 单元
89C3 mov bx,ax CPU内部,ax 寄存器
BB0100 mov bx,1 CPU内部,指令缓冲器

8.3 汇编语言中数据位置的表达

在汇编语言中如何表达数据的位置?

汇编语言中用三个概念来表达数据的位置

  • 立即数(idata)
  • 寄存器
  • 段地址(SA)和偏移地址(EA)

立即数(idata)

对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为:立即数(idata),在汇编指令中直接给出。例如:

mov ax,1 ;对应机器码:B80100

执行结果:(ax) = 1

寄存器

指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。例如:

mov ax,bx ;对应机器码:89D8

执行结果:(ax) = (bx)

段地址(SA)和偏移地址(EA)

指令要处理的数据在内存中,在汇编指令中可用 [X] 的格式给出 EA,SA 在某个段寄存器中。存放段地址的寄存器可以是默认的,或者显性的给出。

我们来看示例:

  • 存放段地址的寄存器是默认的

    • 示例:

      mov ax,[0]
      mov ax,[bx]
      mov ax,[bx+8]
      mov ax,[bx+si]
      mov ax,[bx+si+8]

      注:mov ax,[0] 这么写是可以的,此处相当于 mov ax,ds:[0] 。若写为 mov ax,[9] 则不可以。

      只有中括号里数字为0时,才是特殊情况。此时写 mov ax,[0]mov ax,ds:[0] 均可;若中括号数字大于零,则正确的写法是 mov ax,[9] 或者

      mov bx,9
      mov ax,[bx]

      段地址默认在 ds 中!

    • 示例:

      mov ax,[bp]
      mov ax,[bp+8]
      mov ax,[bp+si]
      mov ax,[bp+si+8]

      段地址默认在 ss 中!

  • 显性的给出存放段地址的寄存器

    • 示例

      mov ax,ds:[bp] ;含义:(ax) = ((ds)*16+(bp)),段寄存器为 ds。
      mov ax,es:[bx] ;含义:(ax) = ((es)*16+(bx)),段寄存器为 es。
      mov ax,ss:[bx+si] ;含义:(ax) = ((ss)*16+(bx)+(si)),段寄存器为 ss。
      mov ax,cs:[bx+si+8] ;含义:(ax) = ((cs)*16+(bx)+(si)+8),段寄存器为 cs。

8.4 寻址方式

当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般被称为寻址方式。

8086CPU有多种寻址方式,我们在前面的课程中都已经用到了,这里我们进行一下总结。

寻址方式 含义 名称 常用格式举例
[idata] EA = idata; SA = (ds) 直接寻址 [idata]
[bx] EA = (bx); SA = (ds) 寄存器间接寻址 [bx]
[si] EA = (si); SA = (ds) 寄存器间接寻址
[di] EA = (di); SA = (ds) 寄存器间接寻址
[bp] EA = (bp); SA = (ss) 寄存器间接寻址
[bx+idata] EA = (bx)+idata; SA = (ds) 寄存器相对寻址 用于结构体:[bx].idata
用于二维数组:[bx][idata]
[si+idata] EA = (si)+idata; SA = (ds) 寄存器相对寻址 用于数组:idata[si]
[di+idata] EA = (di)+idata; SA = (ds) 寄存器相对寻址 用于数组:idata[di]
[bp+idata] EA = (bp)+idata; SA = (ss) 寄存器相对寻址
[bx+si] EA = (bx)+(si); SA = (ds) 基址变址寻址 用于二维数组:[bx][si]
[bx+di] EA = (bx)+(di); SA = (ds) 基址变址寻址
[bp+si] EA = (bp)+(si); SA = (ss) 基址变址寻址
[bp+di] EA = (bp)+(di); SA = (ss) 基址变址寻址
[bx+si+idata] EA = (bx)+(si)+idata; SA = (ds) 相对基址变址寻址 用于表格(结构)中的数组项:[bx].idata[si]
用于二维数组:idata[bx][si]
[bx+di+idata] EA = (bx)+(di)+idata; SA = (ds) 相对基址变址寻址
[bp+si+idata] EA = (bx)+(si)+idata; SA = (ss) 相对基址变址寻址
[bp+di+idata] EA = (bx)+(di)+idata; SA = (ss) 相对基址变址寻址

注:相对对应 idata;基址对应 [bx] 或 [bp];变址对应 [si] 或 [di]。具体举例可参考:[39、数据处理的两个基本问题02][https://www.bilibili.com/video/av28132657/?p=39]

8.5 指令要处理的数据有多长

8086CPU的指令,可以处理两种尺寸的数据,byte 和 word。所以在机器指令中要指明,指令进行的是字节操作还是字操作。

对于这个问题,汇编语言中用一下方法处理。

  1. 通用寄存器名指明要处理的数据的尺寸。
  2. 在没有寄存器名存在的情况下,用操作符 X ptr 指明内存单元的长度,X 在汇编指令中可以为word或byte。(新方法)
  3. 其它方法。

通过寄存器名指明要处理的数据的尺寸

  • 下面的指令中,寄存器指明了指令进行的是字操作:

    mov ax,1 ;0001H放入16位寄存器ax中
    mov bx,ds:[0] ;取ds高8位ds:[1]充当bx高8位;取ds低8位ds:[0]充当bx低8位
    mov ds,ax
    mov ds:[0],ax ;同理,取ax高8位充当ds:[1];取ax低8位充当ds:[0]
    inc ax
    add ax,1000
  • 下面的指令中,寄存器指明了指令进行的是字节操作:

    mov al,1 ;01H放入8位寄存器al中
    mov al,bl
    mov al,ds:[0]
    mov ds:[0],al
    inc al
    add al,100

新方法

  • 下面的指令中,用 word ptr 指明了指令访问的内存单元是一个字单元:

    mov word ptr ds:[0],1 ;0001H
    inc word ptr [bx]
    inc word ptr ds:[0]
    add word ptr [bx],2
  • 下面的指令中,用 byte ptr 指明了指令访问的内存单元是一个字节单元:

    mov byte ptr ds:[0],1 ;01H
    inc byte ptr [bx]
    inc byte ptr ds:[0]
    add byte ptr [bx],2
  • 在没有寄存器参与的内存单元访问指令中,用 word ptr 或 byte ptr 显性地指明所要访问的内存单元的长度是很必要的。否则,CPU无法得知所要访问的单元是字单元,还是字节单元。

    假如我们用Debug查看内存的结果如下:

    2000:1000 FF FF FF FF FF FF FF ......

    那么指令:

    mov ax,2000H
    mov ds,ax
    mov byte ptr [1000H],1 ;将内存偏移地址1000H处的字节单元值修改为1

    将使内存中的内容变为:

    2000:1000 01 FF FF FF FF FF ......

    而指令:

    mov ax,2000H
    mov ds,ax
    mov word ptr [1000H],1 ;将内存偏移地址1000H处的字单元值修改为1

    将使内存中的内容变为:

    2000:1000 01 00 FF FF FF FF ......

    这是因为 mov byte ptr [1000H],1 访问的是地址为 ds:1000H 的字节单元,修改的是 ds:1000H 单元的内容;而 mov word ptr [1000H],1 访问的是地址为 ds:1000H 的字单元,修改的是 ds:1000Hds:1001H 两个单元的内容。

其它方法

有些指令默认了访问的是字单元还是字节单元。比如:push [1000H] 就不用指明访问的是字单元还是字节单元,因为push指令只进行字操作。

8.6 寻址方式的综合应用

  • 一般来说,我们可以用 [bx+idata+si] 的方式来访问结构体中的数据。

    用 bx 定位整个结构体,用 idata 定位结构体中的某一个数据项,用 si 定位数组项中的每个元素。

    为此,汇编语言提供了更为贴切的书写方式。

    如:[bx].idata[bx].idata[si]

8.7 div 指令

  • div 是除法指令(division),使用 div 作除法的时候:

    • 除数:8位或16位,在寄存器或内存单元中

    • 被除数:(默认)放在 AX 或 DX 和 AX 中

      除数 被除数
      8位 16位(AX)
      16位 32位(DX+AX,高16位存放在DX中,低16位存放在AX中)
    • 结果

      运算 余数
      8位 AL AH
      16位 AX DX
  • div 指令格式:

    • div reg
    • div 内存单元
  • 现在我们可以用多种方法来表示一个内存单元了

  • div 指令示例

    • div byte ptr ds:[0]

      含义为:

      (al) = (ax) / ((ds)*16+0)的商;

      (ah) = (ax) / ((ds)*16+0)的余数

    • div word ptr es:[0]

      含义为:

      (ax) = [(dx)*10000H+(ax)]/((ds)*16+0)的商;

      (dx) = [(dx)*10000H+(ax)]/((ds)*16+0)的余数

      注:(dx)*10000H 解读为被除数的高16位。类似于,比如十进制数107可以写成1*100(高位)+7(低位)。

    • div byte ptr [bx+si+8]

      (al) = (ax) / ((ds)*16+(bx)+(si)+8)的商;

      (ah) = (ax) / ((ds)*16+(bx)+(si)+8)的余数

    • div word ptr [bx+si+8]

      (ax) = [(dx)*10000H+(ax)] / ((ds)*16+(bx)+(si)+8)的商;

      (dx) = [(dx)*10000H+(ax)] / ((ds)*16+(bx)+(si)+8)的余数

8.8 伪指令 dd(define double)

  • 前面我们用 db 和 dw 定义字节型数据和字型数据。

  • dd 是用来定义 dword(double word双字)型数据的(2*16=32位)。

  • 说一台电脑某软件拥有64位的性能,必须满足:

    1. CPU是64位;
    2. 操作系统是64位;
    3. 该软件是64位

    缺一不可。否则,低于64位。

  • 示例:

    data segment
    db 1
    dw 1
    dd 1
    data ends

    在 data 段中定义了三个数据:

    第一个数据为 01H,在 data:0 处,占1个字节;

    第二个数据为 0001H,在data:1 处,占1个字;

    第三个数据为 00000001H,在data:3 处,占2个字节;

8.9 dup

dup 是一个操作符,在汇编语言中同 db、dw、dd 等一样,也是由编译器识别处理的符号。它是和 db、dw、dd 等数据定义伪指令配合使用的,用来进行数据的重复。

  • dup 示例

    • db 3 dup (0)

      定义了3个字节,它们的值都是0,相当于 db 0,0,0

    • db 3 dup (0,1,2)

      定义了9个字节,它们是

      0、1、2、0、1、2、0、1、2

      相当于 db 0,1,2,0,1,2,0,1,2

    • db 3 dup ('abc','ABC')

      定义了18个字节,它们是

      'abcABCabcABCabcABC'

      相当于 db 'abcABCabcABCabcABC'

可见,dup 的使用格式如下:

  • db 重复的次数 dup(重复的字节型数据)
  • dw 重复的次数 dup(重复的字型数据)
  • dd 重复的次数 dup(重复的双字型数据)

dup 是一个十分有用的操作符。比如我们要定义一个容量为 200 个字节的栈段,如果不用 dup,则必须用这样的格式:

stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

当然,读者可以用 dd,使程序变得简短一些,但是如果要求定义一个容量为1000字节或10000字节的呢?

如果没有 dup,定义部分的程序就变得太长了。有了dup就可以轻松解决。如下:

stack segment
db 200 dup (0)
stack ends

实验七 寻址方式在结构化数据访问中的应用

Power idea 公司从 1975 年成立一直到 1995 年的基本情况如下。

年份 收入(千美元) 雇员(人) 人均收入(千美元)
1975 16 3 ?
1976 22 7 ?
1977 382 9 ?
1978 1356 13 ?
1979 2390 28 ?
1980 8000 38 ?
...
1995 5937000 17800 ?

下面的程序中,已经定义好了这些数据:

assume cs:codesg
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示 21 年的 21 个字符串
db 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
db 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示 21 年公司总收入的 21 个 dword 型数据
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示 21 年公司雇员人数的 21 个 word 型数据
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends

编程,将 data 段中的数据按如下格式写入到 table 段中,并计算 21 年中的人均收入(取整),结果也按照下面的格式保存在 table 段中。

屏幕快照 2018-08-23 13.53.37

提示:可将 data 段中的数据看成是多个数组,而将 table 中的数据看成是一个结构型数据的数组,每个结构型数据中包含多个数据项。可用 bx 定位每个结构型数据,用 idata 定位数据项,用 si 定位数组项中的每个元素,对于 table 中的数据的访问可采用 [bx].idata 和 [bx].idata[si] 的寻址方式。

注意,这个程序是到目前为止最复杂的程序,它几乎用到了我们以前学过的所有知识和编程技巧。所以,这个程序是对我们从前学习的最好的实践总结。请认真完成。

简答参考:42、寻址方式在结构化数据访问中的应用

猜你喜欢

转载自www.cnblogs.com/doctorx/p/9664646.html