汇编语言中更高级的寻址方式

1.写在前面

我在前面的博客的介绍了汇编中的循环机制,同时也介绍了怎么在一个汇编的程序中定义多个段,循环的机制的就是读取cx中的值,cx寄存器中的值决定循环的次数,今天的博客主要介绍更加高级的寻址的方式。

2.本篇博客的概述

在这里插入图片描述

3.更灵活的定位内存地址的方法

3.1and和or指令

  1. and指令:逻辑与指令,按位进行与运算。通过该指令可将操作的相应的位设为0,其他的位不变。想变0的位置,与上0就行了,其他的与上1就可以不变。
  2. or指令:逻辑或指令,按位进行或运算。通过该指令可将操作的相应的位置设为1,其他的位不变。想要变1的位置,或上1就行了,其他的或上0就可以不变。

3.2关于ASCII码

我们将现实中的一些信息存储到计算机中,就要对其进行编码,将器转化为二进制信息进行存储。而计算机要将这些存储的信息再现实给我们看,就要再对其进行解码。只要编码和解码采用同样的规则,我们就可以将人们理解的信息存入到计算机,再从计算机中取出。而ASCII码就是其中的一种的编码解码的方式。

3.3以字符形式给出的数据

我们先看如下的汇编程序,具体的如下:

assume cs:code,ds:data
data segment
	db 'unIX'
	db 'foRK'
data ends
code segment
start: mov al,'a'
			 mov bl,'b'
			 mov ax,4c00h
			 int 21h
code ends
end start

然后我们编译连接程序,最后通过debug的命令,查看的结果如下:

在这里插入图片描述

可以看到我们的值已经存进去了,这儿Debug也帮我们转换对应的ASCII码值,方便我们查看了。

3.4大小写转换的问题。

先看下面一个问题,在codesg中填写代码,将datasg中的第一个字符串转换为大写,第二个字符串转换成小写。

assume cs:codesg,ds:datasg
datasg segment
	db 'BaSiC'
	db 'iNfOrMaTiOn'
datasg ends
codesg segment
	start:
codesg ends
end start

首先我们先分析一下,同一个字母的大写字符和小写字符对应的ASCII码不同,它们之间相差20H,所以大写转小写就是减去20H,如果是小写转大写就是加上20H。有了上面的分析,于是我们写出如下的代码

assume cs:codesg,ds:datasg
datasg segment
	db 'BaSiC'
	db 'iNfOrMaTiOn'
datasg ends
codesg segment
	start: mov ax,datasg
				 mov ds,ax
				 mov bx,0
				 mov cx,5
			s: mov al,[bx]
				 如果(al) > 61H,则为小写字母的ASCII码,则:sub al,20H
				 mov [bx],al
				 inc bx
				 loop s
				 .
				 .
				 .
codesg ends
end start

但是当我们写到一半的时候,发现我们到现在没有学比较指令,那么怎么判断啊是大写的还是小写,于是我们陷入了一个怪圈,就是一定要比较,但是当我们再分析一下,如果是相差20H,20H就等于100000,于是就有了一种答案就是如果加上20H 只需要将从右往左数第5位变成1即可,如果减去20H只需要将从右往左数第5位变成0即可。于是我们利用And和Or指令即可指定的位置变成1或变成0。有了这些知识即可书写我们的代码了,具体的如下:

assume cs:codesg,ds:datasg
datasg segment
	db 'BaSiC'
	db 'iNfOrMaTiOn'
datasg ends
codesg segment
	start: mov ax,datasg
				 mov ds,ax ;设置ds指向 datasg段
				 mov bx,0 ;设置(bx)=0,ds:bx指向'BaSiC'的第一个字母
				 mov cx,5 ;设置循环次数5,因为'BaSiC'有5个字母
			s: mov al,[bx] ;将ASCII码从ds:bx所指向的单元中取出
				 and al,11011111B ;将al中的ASCII码的第5位置为0,变成大写字母
				 mov [bx],al
				 inc bx
				 loop s
				 mov bx,5 ;设置(bx)=5,ds:bx指向'iNfOrMaTiOn'的第一个字母
				 mov cx,11 ;设置循环次数11,因为'iNfOrMaTiOn'有11个字母
		 s0: mov al,[bx]
		     or al,00100000B ;将al中的ASCII码的第5位置为1,变成小写字母
		     mov [bx],al
		     inc bx
		     loop s0
		     mov ax,4c00h
		     int 21h
codesg ends
end start

于是我们写出了上面代码,我们来编译连接一下,然后运行一下,查看我们的结果如下:

在这里插入图片描述

可以发现我们的大小的转换已经成功了。

3.5[bx+idata]

在前面,我们用[bx]的方式来指明一个内存单元,还可以用一种更为灵活的方式来指明内存单元:[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。

我们来看下指令 mov ax,[bx+200]的含义:

将一个内存单元的内容送入ax,这个内存单元的长度为2个字节(字单元),存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。

数学化的描述为:(ax)=((ds)*16+(bx)+200)

该指令还有如下的格式:

mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200

3.6用[bx+idata]的方式进行数组的处理

有了[bx+idata]的方式表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据。我们通过下面的问题来解答这个问题。

在codesg中填写代码,将datasg中定义的第一个字符串转换为大写,第二个字符串转化为小写

assume cs:codesg,ds:datasg
datasg segment
	db 'BaSiC'
	db 'MinIX'
datasg ends
codesg segment
	start:
codesg ends
end start

按照原来的方法,用[bx]的方式定位字符串的字符,代码段中的程序如下:

mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and al,11011111b
mov [bx],al
inc bx
loop s
mov bx,5
mov cx,5
s0:mov al,[bx]
or al,00100000b
mov [bx],al
inc bx
loop s0

现在,我们有了[bx+idata]的方式,就可以简化上面的代码。这个程序有两个字符串,一个其实地址为0,另外一个起始地址为5。我们可以将这两个字符串看成两个数组,一个从0地址开始存放,另外一个从5开始存放。于是我们就可以用[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符。于是写出下面代码:

mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[bx+5]
or al,00100000b
mov [bx+5],al
inc bx
loop s

这个时候如果是高级语言,C语言描述上面的程序,大致是这样的:

char a[5]="BaSiC";
char b[5]="MinIX";
main(){
  int i;
  i = 0;
  do{
    a[i] = a[i]&0xDF;
    b[i] = b[i]|0x20;
    i++;
  }while(i<5);
}

我们来对比一下,两种语言

C语言:a[i],b[i]

汇编语言:0[bx],5[bx]

通过比较,我们可以发现,[bx+idata]的方式为高级语言实现数组提供了便利机制。

3.7SI和DI

si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。下面的3组指令实现了相同的功能。

mov bx, 0
mov ax,[bx]
mov si,0
mov ax,[si]
mov di,0
mov ax,[di]

下面的三组指令也是

mov bx,0
mov ax,[bx+123]
mov si,0
mov ax,[si+123]
mov di,0
mov ax,[di+123]

问题:用si和di实现字符串’welcome to masm’复制到它后面的数据区中。具体的代码如下:

assume cs:codesg,ds:datasg
datasg segment
	db 'welcome to masm!'
	db '................'
datasg ends

分析:数据在内存中存放,我们在处理数据之前首先要搞清楚数据存储在什么地方,也就是说数据的内存地址。对数据进行复制,先看要复制的数据在什么地方,datasg:0,这是要进行复制的数据的地址。复制到什么地方去?它后面的数据区的偏移地址为16,就是要复制到的地方,清楚了地址之后,我们就可以进行处理了,具体的代码如下:

codesg segment 
start: mov ax,datasg
			 mov ds,ax
			 mov si,0
			 mov di,16
			 mov cx,8
		s: mov ax,[si]
			 mov [di],ax
			 add si,2
			 add di,2
			 loop s
			 
			 mov ax,4c00h
			 int 21h
codesg ends
end start

我们可以利用[bx(si 或 di)+idata]的方式,来使程序变得简洁,程序如下:

codesg segment 
start: mov ax,datasg
			 mov ds,ax
			 mov si,0
			 mov cx,8
		s: mov ax,0[si]
			 mov 16[si],ax
			 add si,2
			 loop s
			 
			 mov ax,4c00h
			 int 21h
codesg ends
end start

3.8[bx+si]和[bx+di]

[bx+si]和[bx+di]的含义相似,我们以[bx+si]为例进行讲解。

[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。

指令 mov ax,[bx+si]的含义如下:

将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中。

数学化的描述为:(ax)=((ds)*16+(bx)+(si))

3.9[bx+si+idata]和[bx+di+idata]

[bx+si+idata]和[bx+di+idata]的含义相似,我们这儿以[bx+si+idata]为例进行讲解。

[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata(即bx中的数值加上si中的数值再加上idata)

指令 mov ax,[bx+si+idata]的含义如下:

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值再加上idata,段地址在ds中。

数学化的描述为:(ax)=((ds)*16+(bx)+(si)+idata)

该指令也可以写成如下的格式(常用)

mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200

3.10不同的寻址方式的灵活应用

通过前面几种定位内存地址的方法,就可以发现:

  1. [idata]用一个常量来表示地址,可用于直接定位一个内存单元。
  2. [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元。
  3. [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元。
  4. [bx+si]用两个变量表示地址。
  5. [bx+si+idata]用两个变量和一个常量表示地址。

例子:编程:将datasg段中每个单词改为大写字母

assume cs:codesg,ds:datasg
datasg segment
	db 'ibm         '
	db 'dec         '
	db 'dos         '
	db 'vax         '
datasg ends
codesg segment
start:
codesg ends
end start

分析:

datasg中的数据的存储结构如下:

在这里插入图片描述

在datasg中定义了4个字符串,每个长度为16个字节,我们可以将这4个字符串看成一个4行16列的二维数组。按照要求,我们需要修改每一个单词,即二维数组的每一行的前3列。

我们需要进行4*3次的二重循环,用变量R定位行,变量C定位列。外层循环按行来进行,内层按列来进行。首先用R定位第1行,然后循环修改R行的前3列;然后再用R定位到下一行,再次循环修改R行的前3列。处理的过程大致如下:

R=第一行的地址
mov cx,4
s0:C=第一列的地址
mov cx,3
s:改变R行,C列的字母为大写
C=下一列的地址
loop s
R=下一行的地址
loop s0

我们用bx来作变量,定位每行的起始地址,用si定位要修改的列,用[bx+si]的方式来对目标单元进行寻址,程序如下:

mov ax,datatsg
mov ds,ax
mov bx,0
mov cx,4
s0: mov si,0
mov cx,3
s:mov al,[bx+si]
and al,110111111b
mov [bx+si],al
inc si
loop s
add bx,16
loop s0

上面的代码问题在于cx的使用,我们进行二重循环,却只用了一个循环计数器,造成在进行内存循环的时候,覆盖了外层循环的循环计数值。多用了一个计数器又不可能,因为loop指令默认cx为循环计数器。这个时候需要我们在每次开始内存循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环loop指令前,再恢复外层循环的cx的数值。可以用寄存器dx来临时保存cx中的数值,改进的程序如下:

mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0:mov dx,cx ;将外层的循环的cx值保存在dx中
mov si,0
mov cx,3 ;cx设置为内层循环的次数

s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,dx ;用dx中存放的外层循环的计数值恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1

上面的程序用dx来暂时存放cx中的值,如果在内存循环中,dx寄存器也被使用,这个时候怎么办?我么似乎没有可以使用的寄存器了,CPU中寄存器的数量毕竟是有限的。而且每个程序中可使用的寄存器都不一样。我们希望寻找一个通用的方案,那么可以使用的就是内存了。可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复。于是写出如下的代码:

assume cs:codesg,ds:datasg
datasg segment
	db 'ibm         '
	db 'dec         '
	db 'dos         '
	db 'vax         '
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0:mov ds:[40H],cx ;将外层的循环的cx值保存在datasg:40H中
mov si,0
mov cx,3 ;cx设置为内层循环的次数

s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ds:[40H] ;用datasg:40H中存放的外层循环的计数值恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1

mov ax,4c00h
int 21h
codesg ends
end start

上面的程序中,用内存单元来保存数据,可是上面的作法还是比较麻烦,如果我们需要保存多个数据的时候,你必须要记住数据存放在哪个单元中。这样程序就很容易混乱。

我们用内存来保存数据,这点是知道了,但是我们用怎样的结构保存这些数据,使得我们的程序更加清晰。一般来说,在需要暂存数据的时候,我们都应该使用栈。于是我们再次改进我们的程序,如下:

assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
	db 'ibm         '
	db 'dec         '
	db 'dos         '
	db 'vax         '
datasg ends

stacksg segment ;定义一个段,用来做栈段,容量为16个字节
	dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment
start:mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0:push cx ;将外层的循环的cx值压栈
mov si,0
mov cx,3 ;cx设置为内层循环的次数

s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx ;从栈顶弹出原cx的值,恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1

mov ax,4c00h
int 21h
codesg ends
end start

4.数据处理的两个基本的问题

第一个问题:处理的数据在什么地方?

第二个问题:要处理的数据有多长?

为了描述上的简洁,我们将使用两个描述性的符合reg来表示一个寄存器,用sreg表示一个段寄存器。

reg的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;

sreg的集合包括:ds、ss、cs、es

4.1bx、si、di和bp

  1. 在8086CPU中,只有这4个寄存器可以在[…]中进行内存单元的寻址。以下的指令是正确的:

    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个寄存器可以单个出现,或只能以4中组合出现: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]
    
  3. 只要在[…]中使用寄存器bp,而指令中没有显性的地给出段地址,段地址就默认在ss中。比如下面的指令

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

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

机器指令都是进行数据处理的指令,处理大致可分为3类:读取、写入、运算。机器指令这一层来说,并不关心数据的值是多少,而关心指令执行的前一刻,它将要处理的数据所在的位置。指令执行前,所有的数据可以在3个地方:CPU内部、内存、端口

机器码汇编指令指令执行前数据的位置
8E1E0000Mov bx,[0]内存,ds:0单元
89C3Mov bx,axCpu内部,ax寄存器
BB0100Mov bx,1Cpu内部,指令缓冲区

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

  1. 立即数(idata)

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

  2. 寄存器

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

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

    指令要处理的数据在内存中,在汇编指令中可用[x]的格式给出EA,SA在某个段寄存器中。

4.4寻址方式

在这里插入图片描述

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

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

  1. 通过寄存器名指明要处理的数据的尺寸。
  2. 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte
  3. 其他方法,例如:push 和 pop指令只能进行字操作

4.6寻址方式的综合应用

下面我们通过一个问题来进一步讨论一下各种寻址方式的作用。

关于DEC公司的一条记录如下:

公司名称: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个字节的产品名称。

但是到了1988年DEC的公司的信息有了如下的变化

Ken Olsen在富豪榜上的排名已经上升到38位

DEC的收入增加了70亿美元。

该公司的著名的产品已经变成了VAX系列的计算机。

于是我们写出下面的简单的代码:

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语言来描述这个程序,大致应该是这样的:

struct company{ //定义一个公司记录的结构体
  char cn[3]; //公司名称
  char hn[9]; //总裁姓名
  int pm; //排名
  int sr; //收入
  char cp[3]; //著名产品
};
main(){
  int i;
  dec.pm = 38;
  dec.sr = dec.sr+70;
  i = 0;
  dec.cp[i] = 'V';
  i = 1;
  dec.cp[i] = 'A';
  i = 2;
  dec.cp[i] = 'X';
  return 0;
}

我们再按照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].0ech,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';
inc si												;i++;

4.7 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存储除法操作的余数。

可以看一个例子:利用除法指令计算1001/100

首先分析一下,被除数1001可用ax寄存器存放,除数100可用8位寄存器存放,也就是说,要进行8位的除法。程序如下:

mov ax,1001
mov bl,100
div bl

程序执行后,(al)=0AH(即10),(ah)=1(余数为1)。

4.8伪指令dd

前面我们用db和dw定义字节型数据和字型数据。dd是用来定义dword(double world 双字)型数据的。比如:

data segment
	db 1
	dw 1
	dd 1
data ends

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

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

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

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

4.9dup

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

可见dup的使用格式如下:

db 重复的次数dup(重复的字节型数据)

dw 重复的次数dup(重复的字型数据)

dd 重复的次数dup(重复的双字型数据)

5.写在最后

本篇博客主要其他的寻址方式,以及一些新的指令。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值