本章对前面的所有内容是具有总结性的。我们知道,计算机是进行数据处理、运算的及其,那么有两个基本的问题就包含在其中:
- 处理的数据在什么地方?
- 处理的数据有多长?
上述的两个问题,在任何一个处理机中都存在。
我们定义的描述性符号:reg
和sreg
为了描述上的简介,用两个描述性的符号:
reg
:表示一个寄存器sreg
:表示一个段寄存器
一、bx、si、di和bp
现在总结一下之前学习过的bx, si和di的用法
(1)在8086CPU中,只有这4个寄存器可以用在[...]
中来进行内存单元的寻址,比如下面的指令都是正确的
; 只有[bx], [si], [di], [bp]这四种形式进行内存单元的寻址
mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]
; [ax], [cx], [dx]等其他寄存器都是错误的
mov ax,[cx] ; 是×的使用
mov ax,[ax] ; 是×的使用
mov ax,[dx] ; 是×的使用
mov ax,[ds] ; 是×的使用
(2)在[...]
中,这4个寄存器可以单个出现,或只能以4种组合出现:bx
和si
、bx
和[di]
、bp
和si
、bp
和di
。
比如下面正确的指令
; 四种单独的寄存器
mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
;4种单独寄存器+idata
mov ax,[bx+idata]
mov ax,[si+idata]
mov ax,[di+idata]
mov ax,[bp+idata]
; 4种组合的情况
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
; 4种组合+idata
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]
下面的指令就是错误的
mov ax,[bx+bp] ; 这是x
mov ax,[si+di] ; 这是x
(3)之前说,不指明段寄存器则默认的是DS,这里有一种例外情况,如果是[bp]
,则默认的段寄存器为ss
mov ax,[bp]
mov ax,[bp+idata]
mov ax,[bp+si]
mov ax,[bp+di]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]
二、机器指令处理的数据在什么地方
绝大部分机器指令都是进行数据处理的指令,处理大致分为3类:
- 读取
- 写入
- 运算
在机器指令这一层来讲,并不关心数据的值是多少,反而关心指令执行前一刻,它将要处理的数据所在的位置。指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口,比如下表所示的指令
机器码 | 汇编指令 | 指令执行前数据的位置 |
---|---|---|
8E1E0000 | mov bx,[0] | 内存,ds:0单元 |
89C3 | mov bx,ax | CPU内部,ax寄存器 |
BB0100 | mov bx,1 | CPU内部,指令缓冲器 |
三、汇编语言中数据位置的表达
在汇编语言中如何表达数据的位置?在汇编语言中用3个概念来表达数据的位置
3.1 立即数(idata)
对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为:立即数,在汇编语言中直接给出
; 直接在机器指令中给出数据:1, 2000h, 00010000b, 'a'
; 在CPU的指令缓冲器中
mov ax,1
add bx,2000h
or bx,00010000b
mov al,'a'
3.2 寄存器
指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
mov ax,bx
mov ds,ax
push bx
mov ds:[0],ax
push ds
mov ss,ax
mov sp,ax
3.3 段地址:偏移地址
指令要处理的数据在内存中,在汇编指令中可用[X]
的格式给出偏移地址,段地址在某个段寄存器中,默认为ds
mov ax,[0]
mov ax,[di]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]
注意: 如果使用的是bp
寄存器,默认的段寄存器为ss
; bp寄存器默认的是ss寄存器
mov ax,[bp]
mov ax,[bp+8]
mov ax,[bp+si]
mov ax,[bp+si+8]
当然,存放段地址的寄存器也可以是显性给出的
; 显性给出段地址
mov ax,ds:[bp]
mov ax,es:[bx+si]
mov ax,cs:[bx+si+8]
四、寻址方式
之前学习到的那些寻址方式非常多,这里就做一个总结吧
五、指令要处理的数据有多长
8086CPU的指令,可以处理两种尺寸的数据:
- byte(字节型)
- word(字型)
所以机器指令中要指明,指令进行的操作是字操作还是字节操作。
(1)通过寄存器指明要处理的数据的尺寸
; 字操作
mov ax,1
mov ds,ax
inc ax
; 字节操作
mov al,1
mov ds:[0],al
inc al
(2)在没有寄存器名存在的情况下,用操作符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
(3)其他方法
有些指令默认了访问的是字单元还是字节单元
; push指令只进行字操作
push [1000H]
六、寻址方式的综合应用
下面通过一个问题来进一步讨论各种寻址方式的作用。
这些数据在内存中以如下方式存放
可以看到,这些数据被存放在seg段中从偏移地址60H开始的位置
- 从seg:60起始以ASCII字符的形式存储了3个字节的公司的名称
- 从seg:60+3起始以ASCII字符的形式存储了8个字节的总裁姓名
- 从seg:60+0C起始存放了一个字型数据,总裁在富翁榜上的排名
- 从seg:60+0E起始存放了一个字型数据,公司的收入
- 从seg:60+10起始存放以ASCII字符的形式存储了3个字节的产品名称
以上是该公司1982年的情况,到了1988年DEC公司的信息有了如下变化
(1)Ken Olsen在富翁榜上的排名升至38位
(2)DEC的收入增加了70亿美元
(3)该公司的著名产品已变为VAX系列计算机
6.1 分析
我们提出的任务是,编程修改内存中的过时数据。
首先,我们应该分析一下要修改的数据,要修改的内容是
(1)(DEC公司记录)的(排名字段)
(2)(DEC公司记录)的(收入字段)
(3)(DEC公司记录)的(产品字段)的(第一个字符)、(第二个字符)、(第三个字符)
从要修改的内容,我们就可以逐步地确定修改的方法
- 要访问的数据是DEC公司的记录,所以,首先要确定DEC公司记录的位置:
R=seg:60
,确定了公司记录的位置后,下面就进一步确定要访问的内容在记录中的位置。 - 确定排名字段在记录中的位置:0CH
- 修改R+0CH处的数据
- 确定收入字段在记录中的位置:0EH
- 修改R+0EH处的数据
- 确定产品字段在记录中的位置:10H。要修改的产品字段是一个字符串(或一个数组),需要访问字符串中的每一个字符,所以要进一步确定每一个字符在
- 修改第一个字符在产品字段中的位置:P=0
- 修改R+10H+P处的数据:P=P+1
- 修改R+10H+P处的数据:P=P+1
- 修改R+10H+P处的数据
6.2 程序
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'
可以看到,8086CPU提供的如[bx+si+idata]
的寻址方式要为结构化数据的处理提供了方便,使得我们在编程的时候,从结构化的角度去看待要处理的数据。
一个结构化的数据包含了多个数据项,数据项的类型又不相同,类型多种多样,如下所示
- 字型数据
- 字节型数据
- 数组(字符串)
可以用[bx+idata+si]
的方式来访问结构体中的数据,用bx定位整个结构体,用idata定位结构体中的某一个数据项,用si定位数组项中的每个元素。
七、div指令
div
是除法指令,使用div
做除法的时候应注意以下问题
除数:有8位和16位两种,在一个reg或内存单元中
被除数:
- 如果除数为8位,被除数则为16位,默认在AX中存放
- 如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX中存放低16位
结果:
- 如果除数为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)=(al)=(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)] / ((es)*16+(bx)+(si)+8)的余数
7.1 例子1
编程,利用除法指令计算10001/100。
首先分析,被除数100001大于,不能使用ax寄存器存放,只能用dx和ax两个寄存器联合起来存放100001,也就是说要进行16位的除法。除数100小于,可以在一个8位寄存器中存放。但是,因为被除数是32位的,除数应为16位,所以要用1个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
7.2 例子2
编程,利用除法指令计算1001/100。
首先分析一下,被除数1001可用ax寄存器存放,除数100可用8位寄存器存放,只要进行8位的除法即可,程序如下
mov ax,1001
mov bl,100
div bl
八、伪指令dd
前面我们用db
和dw
定义字型数据和字节型数据,dd
是用来定义双字型(double word)数据的
data segment
db 1 ; 占一个字节
dw 1 ; 占1个子,2个字节
dd 1 ; 占2个字,4个字节
data ends
用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:0字单元中的高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'
从而知道了,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
有了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
end
编程,将data段中的数据按如下格式写入table段中,并计算21年终的人均收入,结果也按照下面的格式保存在table段中
提示,可将data段中的数据看成是多个数组,而将table中的数据看成是一个结构性数据的数组,每个结构型数据中包含多个数据项。
- 用
bx
定位每个结构性数据 - 用
idata
定位数据项 - 用
si
定位数组项中的每个元素,对于table
中的数据访问可采用[bx].idata
和[bx].idata[si]
的寻址方式。
注意💲:这个程序是到目前为止最复杂的程序,几乎用到了我们学到的所有知识和技巧,是对从前学习的最好的实践总结,认真完成。