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

我们用描述性的符号reg表示寄存器,用sreg表示段寄存器。

1
2
3
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

只有这4个寄存器可以用在[]中进行内存单元寻址。比如

1
mov ax,[bx+si]

在[]中这4种寄存器可以单个出现,如果组合出现,只能出现4种组合,即

1
[bx+si],[bx+di],[bp+si],[bp+di]其他都是错误的。

如果[]中使用bp但是指令中没有给出段地址,则段地址默认在ss中。

比如:

1
mov ax,[bp]含义:(ax) = ((ss)*16+(bp))

机器指令处理的数据在什么地方

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

机器指令并不关心数据的值,而是关心指令执行前一刻,将要处理的数据所在的位置。

一般所处理的数据出现在3个地方:CPU内部,内存,端口。

举例:

1
2
3
mov bx,[0]   内存,ds:0单元
mov bx,ax. CPU内部,ax寄存器
mov bx,1. CPU内部,指令缓冲器

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

1.立即数(idata)

这是直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中)

例:

1
2
3
4
mov ax,1
add bx,2000h
or bx,0001000b
mov al,'a'

2.寄存器

给出相应寄存器名。

1
mov ax,bx

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

指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中(段寄存器可能为默认)

1
mov ax,[bx+8]#带有bx的段地址默认在ds中
1
mov ax [bp+si]#带有bp的段地址默认在ss中

也可以显性给出段寄存器地址比如

1
mov ax,ds:[bp]

寻址方式

image-20230310153340212

由此图可以看出只有寄存器bp对应的段寄存器为ss,其他都是ds。后面四个都是寄存器间接寻址,因为段寄存器都默认了,偏移地址为bx,si等寄存器内部的值。

下面是各种我们讲过的寻址方式的总结:

image-20230310153648434

指令要处理的数据有多长

我们要知道指令进行的是操作还是字节操作。

1.可以看寄存器名指明处理的数据尺寸

1
2
3
4
5
mov ax,1
mov bx,ds:[0]
mov ds,ax
mov ds:[0],ax
add ax 1000

显然这是字操作。一次动了两个字节。

然而下面就是字节操作了:

1
2
3
4
mov al,1
mov al,bl
mov al,ds:[0]
mov ds:[0],al

2.没有寄存器名存在的情况,用操作符X ptr指明内存单元的长度这个X可以是字或字节

比如

1
2
3
4
5
mov word ptr ds:[0],1
inv word ptr [bx]
inv word ptr ds:[0]
add word ptr [bx],2
#显然这里就是用word ptr来指明指令对字单元进行操作

再来看

1
2
3
mov byte ptr ds:[0],1
inc byte ptr [bx]
#这显然就是用byte ptr指明指令访问一个内存单元为字节单元。

没有寄存器参与的内存单元访问指令中,用word ptr或者byte ptr指明所要访问的内存单元的长度是很有必要的。

我们举个例子吧:

对于

image-20230310155741757

1
2
3
mov ax,2000h
mov ds,ax
mov byte ptr [1000h],1

显然变换之后内存中数据为

image-20230310155811791

只变了一个字节,然而我们知道,如果不指明只变了一个字节的话,会一下变一个字。

也就是代码

1
2
3
mov ax,2000h
mov ds,ax
mov word ptr [1000h],1

显然这样就会变为

image-20230310155920028

注:push [1000h]就不用指明字或字节,因为push只能进行字操作!

寻址方式的综合应用

来看一个实际问题:

image-20230310161007023

这些数据在下图中这样存放

image-20230310161033578

image-20230310161240367

所以我们要修改排名字段,收入字段和产品字段的第一,第二和第三个字符。

如下是具体的步骤!

image-20230310162041887

我打出程序的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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#用来定位字符
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语言示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct company{
char cn[3];
char nn[9];#总裁姓名
int pm;#排名
int sr;#收入
char cp[3]#著名产品
};
struct company dec={"DEC","Ken Olsen",137,40,"PDP"};#定义了一个结构体变量存储数据,内存中将会存有一条公司的记录
main()
{
int i;
dec.pm=38;
dec.sr=dec.sr+70;
i = 0;
dec.cp[i]='V';
i++;
dec.cp[i]='A';
i++;
dec.cp[i]='X';
return 0;
}

下面按照C语言风格用汇编去改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

mov si,0
mov byte ptr [bx].10h[si],'V'
#对应C语言是dec.cp[i]='V'
inc si
mov byte ptr [bx].10h[si],'A'
#dec.cp[i]='A'
inc si
mov byte ptr [bx].10h[si],'X'

我们可以看出数组中找到每个元素可以写成

1
[bx].idata[si]

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存储余数。

我总结了一下上述的特点,方便记忆:小的就只存AX,大的就存AX和DX但是先AX后DX,比如被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

1
2
div byte ptr ds:[0]#存放除数,只需要指明除数即可,因为被除数与商都在AX中,这里除数为8位,所以都是AX
含义:(al) = (ax)/((ds)*16+0)的商

下面看除数是16位的

1
2
3
div word ptr es:[0]#显然这里除数是16位的,被除数32位高放DX
含义:(ax)=[(dx)*10000h+(ax)]/((es)*16+0)的商
(dx)=..............................的余数

下面来练习一个题目:

编程,利用除法指令计算100001/100

首先被除数100001>65535=2^16不能用ax单独存放,所以除数为16位(被除数大于16位了)

除数100<255可以放在8位寄存器中,但是这里要用16位寄存器存放除数100。

代码如下:

1
2
3
4
mov dx,1
mov ax,86A1h#也就是100001的16进制186A1h取后4个数对应二进制的8位。(dx)*10000h+(ax)=100001
mov bx,100
div bx#除数指明,bx为16位寄存器

可以自行执行。

再来练习一道利用除法计算1001/100

1
显然1001是16位以内,100是8位以内,所以被除数和除数分别为16位和8位。很正规

程序如下:

1
2
3
mov ax,1001
mov bl,100
div bl

伪指令

我们用db和dw来定义字节型数据和字型数据。dd是用来定义dword(double word,双字)数据的。

比如:

1
2
3
4
5
data segment
db 1
dw 1
dd 1
data ends

显然定义了3个数据

第一个数据为01h,在data:0处,占一个字节。

第二个数据为0001h,在data:1处,占一个字。

第三个数据为00000001h,在data:3处,占2个字。

image-20230310190508678

1
2
3
4
5
data segment
dd 100001#被除数,显然为32位
dw 100#除数,显然位8位,但要在16位寄存器中存放。取决于被除数。
dw 0
data ends

解决代码如下:

1
2
3
4
5
6
mov ax,data
mov ds,ax
mov ax,ds:[0]#ds:0字单元中的低16位存储在ax中
mov dx,ds:[2]#高16位放在dx中
div word ptr ds:[4]#用ds:ax中的32位数据除以ds:4字单元中的数据(除数放在16位寄存器中)
mov ds:[6],ax#将商存储在ds:6字单元中,因为默认商就在ax中。

dup

这是一个操作符,同db,dw,dd一样,与db,dw,dd等伪指令配合使用进行数据的重复。比如

1
db 3 dup (0)

相当于

1
db 0,0,0定义了三个字节,他们的值都是0!
1
db 3 dup (0,1,2)#即三个0,1,2

定义了9个字节,它们是0,1,2,0,1,2,0,1,2相当于

1
db 0,1,2,0,1,2,0,1,2

再来一个

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

一般用来定义长栈段

比如

1
2
3
stack segment
db 200 dup (0)
stack ends