《汇编语言(第四版)》—王爽 第七章更灵活的定位内存地址的方法
第七章更灵活的定位内存地址的方法
7.1、and和or指令
and指令
逻辑与指令,按位进行与运算(两个都为1时结果才为1)
mov al,01100011B al是一个8位的寄存器
and al,00111011B al是一个8位的寄存器
执行后 al = 00100011B
and指令的功能:通过该指令可以将操作对象的对应位设为0,其他位不变------>原理:就是将对应位置的数字与0进行逻辑与运算,则该位一定会为0
例如:将al中的第五位设置为0 and al,11011111B
将al中的第零位设置为0 and al,11111110B
or指令
逻辑或指令,按位进行或运算(只要有1结果就为1)
mov al,01100011B al是一个8位的寄存器
or al,00111011B al是一个8位的寄存器
执行后 al = 01111011B
or指令的功能:通过该指令可以将操作对象的对应位设置为0,其他位不变------>原理:就是将对应位置的数字与1进行逻辑或运算,由于逻辑或运算中有1则结果为1,顾此时该位置一定为1
例如:将al中的第五位设置为1 or al,00100000B
将al中的第零位设置为1 or al,00000001B
7.2、关于ASCII码
(上述的ASCII表是十进制请款下的表)
一个文件编辑过程中,就包含着按照ASSCII编码规则进行的编码和解码,例如在ASCII中,用61H表示"a",用62H表示"b"等等
7.3、以字符形式给出的数据
我们在编辑程序中,用’…'的方式指明数据是以字符形式给出的,编译器将把他们转化为相对应的ASCII码
assume cs:code,ds:data
data segment
db 'unIX' 定义字节型数据一个字符占8位
db 'foRK'
data ends
code segment
start: mov al,'a'
mov bl,'b'
mov ax,4c00H
int 21H
code ends
end start
从指令可以看出ASCII码和内存单元的对应关系
7.4、大小写转换的问题
要改变一个字母的大小写,实际上就是改变它所对应的ASCII码
大写 二进制 十六进制 小写 二进制 十六进制
A 01000001 41H a 01100001 61H
B 01000010 42H b 01100010 62H
C 01000011 43H c 01100011 63H
D 01000100 44H d 01100100 64H
观察上述的大小写二进制表达,可以发现大小写字母的二进制表达中只是第五位的0或者1的区别
我们可以将大写字母的ASCII码的值加上20H变为小写字母,同理小写字母变大写字母即为减少20H
提出问题:如何将数据段中的第一个字符串“BaSiC”中的小写字母变成大写,第二个字符串“iNfOrMaTiOn”中的大写字母变成小写
assume cs:codesg , ds:datasg
data segment
db 'BaSic'
db 'iNfOrMaTiOn 'datasg ends
data ends
code segment
start:mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
如果(al)>61H,则为小写字母的ASCII码,则:sub al,20Hmov [bx],al
inc bx
loop s
:
codesg ends
end start
很显然判断将用到我们目前还没有学到的指令,那我们尝试着用已经学习到的知识来进行操作
思路:
由于上述分析可知大写字母和小写字母的二进制表示中只有第五位是不一样的,小写字母为1,而大写字母位0,那么进一步思考,如何将第五位的0或者是1,进行修改成为对应的值呐?
很显然and指令和or指令可以做到
因此我们可以用这两个指令来实现转换
一个字母无论它是小写还是大写只要第五位为0,它就必将为大写字母,第五个位置为1,它就必将为小写字母
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] ;将ASCI工码从ds:bx所指向的单元中取出
and al,11011111B ;将al中的ASCII码的第5位置为0,变为大写字母
mov [bx],al ;将转变后的ASCII码写回原单元
inc bx ;(bx)加1,ds: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
7.5、[bx+idata]
[bx+idata]表示一个内存单元,他的偏移地址为(bx) + idea
实例说明:mov ax,[bx+200]
含义:将一个内存单元的内容送到ax中,这个内存单元长度为两个字节,存放一个字,偏移地址为bx中的数值加上200,段地址在ds中
数字化描述:(ax) = ((ds)*16 + (bx)+200)
常用的表达方式有:
- mov ax , [idata + bx]
- mov ax , idata[bx]
- mov ax , [bx].idata
课本问题7.1分析:
分析一:mov ax,[bx]是访问的字单元的段地址在ds中,即(ds)=2000H,偏移地址在bx中,(bx)=1000H
分析二:mov cx,[bx+1]访问的字单元在段地址ds中,(ds)=2000H;偏移地址为(bx)+1 = 1001H
分析三:add cx,[bx+2]访问的字单元在段地址ds中,(ds)=2000H;
偏移地址为(bx)+ 2 = 1002H
7.6、用[bx+idata]的方式进行数组的处理
在代码段中填写代码,将数据段中定义的第一个字符串的字母转化为大写,第二个字符串的字母转化为小写
改进前的代码:(省略了字符串的定义部分)
mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and a1,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
分析:我们可以将两个字符串看作为数组(两个字符串长度都为5,编号从0开始),
此时两个字符串的起始地址为[0+bx],[5+bx],bx表示的是在数组中的相对偏移地
址,0和5是起始的地址
改进后完整代码:
assume cs:code,ds:data
data segment
db 'BaSic'
db 'MinIx'
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[0+bx]
and al,11011111b
mov [0+bx],al
mov al,[bx+5]
or al,00100000b
mov [bx+5],al
inc bx
loop s
mov ax,4c00H
int 21H
code ends
end start
该程序用C语言的表现形式为:
C语言表示定位的方式为 a[i],b[i]
汇编语言表示定位方式为 0[bx],5[bx]
通过比较发现,[bx+idata]的方式为高级语言实现数组提供了便利机制
7.7、SI和DI
SI和 DI是8086CPU中和 bx 功能相近的寄存器,SI和 DI不能够分成两个8位寄存器来使用。
1> mov bx,0 mov ax,[bx]
2> mov si,0 mov ax,[si]
3> mov di,0 mov ax,[di]
这三个指令实现了相同的功能
结合上一小节所学到的指令
1> mov bx,0 mov ax,[bx+idata]
2> mov si,0 mov ax,[si+idata]
3> mov di,0 mov ax,[di+idata]
这三个指令同样也实现了相同的功能
问题7.2分析:
我们编写的程序大都是进行数据的处理,而数据在内存中存放,所以我们在处理数据
之前首先要搞清楚数据存储在什么地方,也就是说数据的内存地址。
现在我们要对datasg段中的数据进行复制,先来看一下要复制的数据在什么地方,
datasg:0,这是要进行复制的数据的地址。那么复制到哪里去呢?它后面的数据区。
“welcome to masm!”从偏移地址0开始存放,长度为16个字节,所以,它后面的数
据区的偏移地址为16,就是字符串“.....”存放的空间。
清楚了地址之后,我们就可以进行处理了。我们用ds:si 指向要复制的源始字符串,
用ds:di指向复制的目的空间,然后用一个循环来完成复制。
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
注意:因为si,di,bx均为16位的寄存器,则一次是传输两个字节
思考:如何使得代码段变得更加的简介
从上述的代码中可以看出,我们在赋值时用到了两个寄存器,程序较为复杂,且两个字符串的长度均为16,我们可以想到用数组的方式去实现,即为[bx(si或者di) + idata]的方法,此时idata为数组与数组之间的偏移地址,寄存器中的位数组中的相对偏移地址
优化后的代码:
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0
mov cx,8
s: mov ax,o [si]
mov 16[si],ax
add si,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
7.8、[bx+si]和[bx+di]
[bx+si]表示一个内存单元,他的偏移地址为(bx)+(si),[bx+di]同理
mov ax,[bx+si]的数字化表示形式为 (ax) = ((ds) * 16 + (bx)+(si))
mov ax,[bx+si]的其他表示形式:mov ax,[bx] [si]
实例练习:
1> mov ax,[bx+si]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1000H;指令执行后(ax)=00BEH。
2> mov cx, [bx+si]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1001H;指令执行后(cx)=0600H。
3> add cx,[bx+di]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)=1002H;指令执行后(cx)=0606H。
7.9、[bx+si+idata]和[bx+di+idata]
[bx+si+idata]和[bx+di+idata]含义相似
[bx+si+idata]和[bx+di+idata]都表示内存单元
数字化的描述为(ax) = ((ds) * 16 + (si) + idata)
指令mov ax,[bx+si+idata]其他表示形式
- mov ax,[bx+idata+si]
- mov ax,[idata+bx+si]
- mov ax,idata[bx] [si]
- mov ax,[bx].idata[si]
- mov ax,[bx] [si].idata
实例练习:
1> mov ax, [bx+2+si]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)+2=1002H;指令执行后(ax)=0006H。
2> mov cx,[bx+2+si]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)+2=1003H;指令执行后(cx)=6A00H。
3> mov bx,[bx+2+di]
访问的字单元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)+2=1004H;指令执行后(bx)=226AH。
7.10、不同的寻址方式灵活使用
如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式),就可以发现:
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
- [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
- [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
- [bx+si]用两个变量表示地址;
- [bx+si+idata]用两个变量和一个常量表示地址。
下面我们将以具体的问题来体会CPU这样处理的用意
问题7.6:将数据段中的每个单词的头一个字母改写为大写字母
由图片分析可知:每个单词的第一个字母都在3的位置上,一共有六行数据,则此时列数是固定值,行数需要进行循环,所以此时需要的结构就是用[bx]表示行数,用idata表示列数3,即用到结构[bx+idata]
assume cs:codesg,ds:datasg
datasg segment
db '1. file ' ;定义为16位 空格补齐
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6
s:mov al,[bx+3] ;注意:单位是字节,所以是al
and al,11011111b ;使得第五位为0,则一定为大写
mov [bx+3],al
add bx,16
loop s
mov ax,4c00H
int 21H
codesg ends
end start
问题7.7:将代码段中的每一个单词改为大写字母
由上图分析可知:我们需要将每个字母修改为大写字母,就相当于是需要一个4*3的循环(嵌套)
- 外层循环按照行来进行
- 内存按列来进行
大致思路如下:
R=第一行的地址;
mov cx, 4
s0 : c = 第一列的地址
mov cx, 3
s : 改变R行,c列的字母为大写
c=下一列的地址;
loop s
R = 下一行的地址
loop s0
我们用bx来做变量,定位每行的起始地址,用si来表示要修改的列,用[bx + si]的方式来对目标单元寻址
注意:如果按照上述文字表述编写指令并执行时就会出现错误,进入死循环------>原因是:问题就在于cx的使用,我们进行二重循环,却只用了一个循环计数器,造成了内存的时候覆盖了外层的循环计数值
在内层执行loop s完毕时cx的值为0,但是此时还需要执行外层的循环loop s0使得cx的值为(cx) = (cx) - 1,这样就会使得cx变成FFFFH,判断此时cx中的内容不为0,则继续执行循环,因此就会出现死循环
解决方案:在每次开始内存循环时,先将外层循环的值保存起来
改进后的程序
assume cs:codesg,ds:datasg
datasg segment
db 'ibm.............'
db 'dec.............'
db 'vax.............'
db 'dos.............'
datasg ends
codesg segment
start: mov ax,datasg ;把数据段的地址给到AX寄存器
mov ds,ax ;ds为数据位的寄存器,ds和偏移地址构成
mov bx,0 ;bx寄存器设置为0
mov cx,4 ;外层循环指的是层数的循环
s0:mov dx,CX ;将cx中的数据放入到dx中
mov si,0 ;si寄存器和bx寄存器的时候几乎一致
mov cx,3 ;内层循环为列的循环
S: mov al,[bx+si] ;si控制列bx控制行
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,dx
loop s0
mov ax,4c00H
int 21H
codesg ends
end start
上面的程序用dx来暂时存放在cx中的值,如果在内层循环中,dx寄存器也被使用,我们就必须再寻找其他存储的位置------>内存
再次改进后的程序
assume cs:codesg,ds:datasg
datasg segment
db 'ibm.............'
db 'dec.............'
db 'vax.............'
db 'dos.............'
dw 0 ;定义一个字用来存放cx
datasg ends
codesg segment
start: mov ax,datasg ;把数据段的地址给到AX寄存器
mov ds,ax ;ds为数据位的寄存器,ds和偏移地址构成
mov bx,0 ;bx寄存器设置为0
mov cx,4 ;外层循环指的是层数的循环
s0:mov ds:[40H],CX ;将cx中的数据放入到内存单元中
mov si,0 ;si寄存器和bx寄存器的时候几乎一致
mov cx,3 ;内层循环为列的循环
S: mov al,[bx+si] ;si控制列bx控制行
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ds:[40H]
loop s0
mov ax,4c00H
int 21H
codesg ends
end start
一般来说,在需要暂存数据的时候,我们都应该使用栈
继续修改--->使用栈段
assume cs:codesg,ds:datasg
datasg segment
db 'ibm.............'
db 'dec.............'
db 'vax.............'
db 'dos.............'
datasg ends
stacksg segment ;定义一个段,用来做栈段
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
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx
loop s0
mov ax,4c00H
int 21H
codesg ends
end start
问题7.9:将代码段中的每个单词的前四个字母改为大写字母
思路:我们需要进行4x4次的二重循环,用变量R定位行,常量3定位每行要修改的起始列,变量C定位相对于起始列的要修改的列。外层循环按行来进行,内层按列来进行。我们首先用R定位第1行,循环修改R行的3+C(O≤C≤3)列;然后再用R定位到下一行,再次循环修改R行的3+C(O≤C≤3)列……,如此重复进行
assume cs:codesg,ds:datasg,ss:stacksg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db '1. display......'
db '2. brows........'
db '3. replace......'
db '4. modify.......'
datasg 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
mov si,0
mov cx,4
s: mov al,[bx+3+si]
and al,11011111b
mov [bx+3+si],al
inc si
loop s
add bx,16
pop cx
loop s0
mov ax,4c00H
int 21H
codesg ends
end start