数据处理的两个基本问题
-
本章对前面的所有内容是具有总结性的。我们知道,计算机是进行数据处理,运算的机器,那么有两个基本的问题就包含在内:
- 处理的数据在什么地方?
- 要处理的数据有多长?
-
这两个问题,在机器指令中必须给以明确或隐含的说明,否则计算机就无法工作。
-
本章中,我们就要针对8086CPU对这两个基本问题进行讨论。虽然讨论是在8086CPU的基础上进行的,但是这两个基本问题却是普遍的,对任何一个处理器都存在。
-
我们定义的描述性符号:reg和sreg
-
为了描述上的简洁,在以后的课程中,我们将使用两个描述性的符号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中,只有这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,[ex]
mov ax,[ax]
mov ax,[dx]
mov ax,[ds]
-
在“[…]”中,这4个寄存器(bx,bp,si,di)可以单个出现,或只能以四种组合出现:
bx和si、bx和di、bp和si、bp和di
比如下面的指令是正确的:
mov ax,[bx] mov ax,[si]
mov ax,[di] 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]
下面的指令是错误的:
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))
mov ax,[bp+si+idata] 含义:(ax) = ((ss)*16+ (bp) + (si)+idata)
-
机器指令处理的数据所在位置
-
绝大部分机器指令都是进行数据处理的指令,处理大致可分为三类:
读取、写入、运算
-
在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,他将要处理的数据所在的位置。
-
指令在执行前,所要处理的数据可以在三个地方:CPU内部,内存,端口(端口我们将在后面的课程中进行讨论)举例:
汇编语言中数据位置的表达
-
在汇编语言中如何表达数据的位置?
-
汇编语言中用三个概念来表达数据的位置:
- 立即数(idata)
- 寄存器
- 段地址(SA)和偏移地址(EA)
-
1.立即数
对于直接包含在机器指令中的数据(执行前在CPU的指令缓存器中),在汇编语言中称为:立即数(idata),在汇编指令中直接给出。例如: mov ax,1 对应机器码:B80100 执行结果:(ax)=1
-
2.寄存器
指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。例如:mov ax,bx 对应的机器码:89D8
执行结果:(ax)=(bx)
-
3.段地址(SA)和偏移地址(EA)
指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。
-
存放段地址的寄存器可以是默认的
比如:
mov ax,[0]
mov ax,[bx]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]
以上这些段地址默认在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))
mov ax, es : [bx] 含义:(ax) = ( (es) *16+ (bx))
mov ax,ss:[bx+si] 含义:(ax) = ( (ss)*16+(bx) + (si))
mov ax,cs:[bx+si+8] 含义:(ax) = ( (cs) *16+ (bx) + (si) + 8)
-
寻址方式
- 当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址, 这种定位内存单元的方法一般被称为寻址方式。
- 8086CPU有多种寻址方式,我们在前面的课程中都已经用到了,这里进行一下总结。
指令要处理的数据有多长?
-
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 al, 100
-
在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编 指令中可以为word或byte。
例如,下面的指令中,用word ptr指明了指令访问的内存单元是一个字单元。
-
mov word ptr ds: [0],1
-
inc word ptr [bx]
-
inc word ptr ds: [0 ]
-
add word ptr [bx],2
下面的指令中,用byte ptr指明了指令访问的内存单元是一个字 节单元。
-
mov byte ptr ds: [0],1
-
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
那么指令:
mov ax,2000H
mov ds,ax
mov byte ptr [1000H],1
将使内存中的内容变为:
2000: 1000 01 FF FF FF FF FF …
而指令:
mov ax,2000H
mov ds,ax
mov word ptr [1000H],1
将使内存中的内容变为:
2000: 1000 01 00 FF FF FF FF …
-
这是因为mov byte ptr [1000H],l访问的是地址为ds:1000H的字节单元,修改的是 ds:1000H单元的内容;
-
而mov word ptr [1000H],l访问的是地址为ds:1000H的字单元,修改的是ds:1000H和ds:1001H两个单元的内容。
-
-
其他方法
有些指令默认了访问的是字单元还是字节单元,比如,push [1000H]就不用指明访问 的是字单元还是字节单元,因为push指令只进行字操作。
-
寻址方式的综合应用
-
下面我们通过一个问题来进一步讨论一下各种寻址方式的作用。
关于DEC公司的一条记录(1982年)如下:
公司名称:DEC
总裁姓名:Ken Olsen
排 名:137
收 入:40(40亿美元)
著名产品:PDP(小型机)
这些数据在内存中以如图所示的方式存放。
可以看到,这些数据被存放在seg段中从偏移地址60H起始的位置,从seg:60起始以ASCII字符的形式存储了3个字节的公司名称;从seg:60+3起始以ASCII字符的形式存储了9个字节的总裁姓名;从seg:60+0C起始存放了一个字型数据,总裁在富翁榜上的排名;从seg:60+0E起始存放了一个字型数据,公司的收入;从seg:60+10起始以ASCII字符的形式存储了3个字节的产品名称。
以上是该公司1982年的情况,到了 1988年DEC公司的信息有了如下变化。
(1) Ken Olsen在富翁榜上的排名已升至38位;
(2) DEC的收入增加了 70亿美元;
(3) 该公司的著名产品已变为VAX系列计算机。
我们提出的任务是,编程修改内存中的过时数据。
-
首先,我们应该分析一下要修改的数据。
要修改内容是:
(1) (DEC公司记录)的(排名字段)
(2) (DEC公司记录)的(收入字段)
(3) (DEC公司记录)的(产品字段)的(第一个字符)、(第二个字符)、(第三个字符)
-
从要修改的内容,我们就可以逐步地确定修改的方法。
(1) 我们要访问的数据是DEC公司的记录,所以,首先要确定DEC公司记录的位置:R=seg:60
确定了公司记录的位置后,下面就进一步确定要访问的内容在记录中的位置。
(2) 确定排名字段在记录中的位置:0CH。
(3) 修改R+0CH处的数据。
(4) 确定收入字段在记录中的位置:0EH。
(5) 修改R+0EH处的数据。
(6) 确定产品字段在记录中的位置:10H。
要修改的产品字段是一个字符串(或一个数组),需要访问字符串中的每一个字符。所 以要进一步确定每一个字符在字符串中的位置。
(7) 确定第一个字符在产品字段中的位置:P=0。
(8) 修改R+10H+P处的数据;P=P+1。
(9) 修改R+10H+P处的数据;P=P+1。
(10)修改R+10H+P处的数据。
-
根据上面的分析,程序如下。
mov ax, seg
mov ds, ax
mov bx, 60h ;确定记录地址,ds :bx
mov word ptr [bx+0ch] , 38 ;排名字段改为38
add word ptr [bx+0eh], 70 ;收入字段增加70
mov si, 0 ;用si来定位产品字符串中的字符
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语言的话,我们可以用c语言来描述这个程序,大致应该是这样的:
-
我们再按照C语言的风格,用汇编语言写一下这个程序,注意和C语言相关语句的比对:
mov ax,seg mov ds,ax mov bx, 60h ;记录首址送BX mov word ptr [bx].0ch, 38 ;排名字段改为38 ;C: dec.pm=38; add word ptr [bx].0eh, 70 ;收入字段增加70 ;C: dec.sr=dec.sr+70; ;产品字段改为字符串'VAX' mov si,0 ;C: i=0; mov byte ptr [bx].10h[si], 'V' ;dec.cp[i]='V' ; inc si ;i++; mov byte ptr [bx].10h[si], 'A' ;dec.cp[i]='A' ; inc si ;i++; mov byte ptr [bx].10h[si], 'X' ;dec.cp[i]='X' ;
-
我们可以看到,8086CPU提供的如[bx+si+idata]的寻址方式为结构化数据的处理提供了方便。
使得我们可以在编程的时候,从结构化的角度去看待所要处理的数据。
从上面可以看到,一个结构化的数据包含了多个数据项,而数据项的类型又不相同,有的是字型数据,有的是字节型数据,有的是数组(字符串)。
一般来说,我们可以用[bx+idata+si]的方式 来访问结构体中的数据。
用bx定位整个结构体,用idata定位结构体中的某一个数据项, 用si定位数组项中的每个元素。
为此,汇编语言提供了更为贴切的书写方式,如: [bx].idata、[bx].idata[si]。
在C语言程序中我们看到,如:dec.cp[i], dec是一个变量名,指明了结构体变量的 地址,cp是一个名称,指明了数据项cp的地址,而i用来定位cp中的每一个字符。汇编 语言中的做法是:bx.10h[si]。看一下,是不是很相似?
div指令
-
div是除法指令,使用div做除法的时候应注意以下问题。
(1) 除数:有8位和16位两种,在一个reg或内存单元中。
(2) 被除数:默认放在AX或DX和AX中。
如果除数为8位,被除数则为16位, 默认在AX中存放;
如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
(3) 结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;
如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
-
div的指令格式如下:
- div reg
- 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) ]/ ( (es) *16+0)的商
(dx) = [ (dx) *10000H+ (ax) ] / ( (es) *16+0)的余数
-
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)的余数
-
-
编程,利用除法指令计算100001/100。
-
首先分析一下,被除数100001大于65535,不能用ax寄存器存放,所以只能用dx 和ax两个寄存器联合存放100001,也就是说要进行16位的除法。
除数100小于255,可 以在一个8位寄存器中存放,但是,因为被除数是32位的,除数应为16位,所以要用一 个16位寄存器来存放除数100。
因为要分别为dx和ax赋100001的高16位值和低16位值,所以应先将100001表示 为16进制形式:186A1H。程序如下:
mov dx,1 mov ax,86A1H ; (dx) *10000H+(ax)=100001 mov bx,100 div bx
程序执行后,(ax)=03E8H(即1000), (dx)=1(余数为1)。可自行在Debug中实践
-
编程,利用除法指令计算1001/100。
首先分析一下,被除数1001可用ax寄存器存放,除数100可用8位寄存器存放,也就是说,要进行8位的除法。程序如下。
mov ax,1001 mov bl,100 div bl
程序执行后,(al)=0AH(即10), (ah)=1(余数为1)。可自行在Debug中实践。
伪指令dd
-
前面我们用db和dw定义字节型数据和字型数据。
-
dd是用来定义dword(double word,双字)型数据的(32位)。比如:
data segment db 1 dw 1 dd 1 data ends
在data段中定义了 3个数据:
第一个数据为01H,在data:0处,占1个字节;
第二个数据为0001H,在data:1处,占1个字;
第三个数据为00000001H,在data:3处,占2个字。
-
用div计算data段中第一个数据除以第二个数据后的结果,商存在第三个数据的存储单元中。
data segment dd 100001 dw 100 dw 0 data ends
思考后看分析。
-
分析:
data段中的第一个数据是被除数,为dword(双字)型,32位,所以在做除法之前,用 dx和ax存储。应将data:0字单元中的低16位存储在ax中,data:2字单元中的高16位存 储在dx中。
-
程序如下:
mov ax, data mov ds, ax mov ax,ds:[0] ;ds:0字单元中的低16位存储在ax中 mov dx,ds:[2] ;ds:2字单元中的高16位存储在dx中 div word ptr ds:[4] ;用dx:ax中的32位数据除以ds:4字单元中的数据 mov ds;[6] ,ax ;将商存储在ds:6字单元中
-
dup
-
dup是一个操作符,在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的 符号。
-
它是和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复。
-
示例:
-
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年的基本情况如下。
-
下面的程序中,已经定义好了这些数据:
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个字符串 dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514 dd 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段中。
提示,可将data段中的数据看成是多个数组,而将table中的数据看成是一个结构型 数据的数组,每个结构型数据中包含多个数据项。可用bx定位每个结构型数据,用idata 定位数据项,用si定位数组项中的每个元素,对于table中的数据的访问可采用[bx].idata 和[bx].idata[si]的寻址方式。
-
-
我们要考虑几个问题:
-
源数据在哪里?ds
-
年份,收入,雇员的存放位置
年份:0~53H 收入:54H~0A7H 雇员的存放位置:0A8H~0D1H
-
-
目标存放位用什么来表示?es
- 年份,收入,雇员,平均收入的存放位置
-
观察数据类型和如何利用偏移寻址
-
-
初始化阶段
-
mov ax,data mov ds,ax mov ax,table ;这里因为data已被占用 mov es,ax mov bx,0 ;bx确定年份和收入 mov si,0 ;si确定人数 mov di,0 ;di确定的是每行的列数 mov cx,21 ;二十一次循环,大家可想而知
-
每次循环要执行的任务
- 存放年份
- 存放公司总收入
- 存放公司人数
- 计算人均收入并存放
-
存放年份
mov al,[bx] mov es:[di],al mov al,[bx+1] mov es:[di+1],al mov al,[bx+2] mov es:[di+2],al mov al,[bx+3] mov es:[di+3],al
-
存放公司总收入
mov ax,54h[bx] ;第一个'年收入'的段基址为54H mov dx,56h[bx] mov es:5h[di],ax mov es:7h[di],dx
-
存放公司人数
mov ax,0A8H[si] ;第一个'人数'的段基址为0A8H mov es:0Ah[di],ax
-
计算人均收入并存放
mov ax,54h[bx] mov dx,56h[bx] ;这两句是初始化被除数 div word ptr ds:0A8h[si] ;除以人数 mov es:odh[di],ax ;将商放入指定位置
-
为下一次循环时存放数据做准备
add bx,4 ;bx确定年份和收入 add si,2 ;si确定人数 add di,16 ;di确定的是每行的列数
-
存放公司总收入
mov ax,54h[bx] ;第一个'年收入'的段基址为54H mov dx,56h[bx] mov es:5h[di],ax mov es:7h[di],dx
-
存放公司人数
mov ax,0A8H[si] ;第一个'人数'的段基址为0A8H mov es:0Ah[di],ax
-
计算人均收入并存放
mov ax,54h[bx] mov dx,56h[bx] ;这两句是初始化被除数 div word ptr ds:0A8h[si] ;除以人数 mov es:odh[di],ax ;将商放入指定位置
-
为下一次循环时存放数据做准备
add bx,4 ;bx确定年份和收入 add si,2 ;si确定人数 add di,16 ;di确定的是每行的列数
-