汇编学习笔记(6) -- 更灵活的定位内存地址的方法
and 和 or指令
and指令:逻辑与指令,按位进行与运算。
mov al,01100011B
and al,00111011B
执行后 al = 00100011B
0 1 1 0 0 0 1 1
0 0 1 1 1 0 1 1
-------------------
0 0 1 0 0 0 1 1
上下两个同时为1时,结果才为1
将al的第6位设为0的指令是: and al,10111111B
将al的第7位设为0的指令是: and al,01111111B
将al的第0位设为0的指令是: and al,11111110B
or指令:逻辑或指令,按位进行或运算
mov al,01100011B
or al, 00111011B
执行后: al= 01111011B
0 1 1 0 0 0 1 1
0 0 1 1 1 0 1 1
-------------------
0 1 1 1 1 0 1 1
上下两个只要一个为1,结果就为1
将al的第6位设为1的指令是: or al,01000000B
将al的第7位设为1的指令是: or al,10000000B
将al的第0位设为1的指令是: or al,0000001B
关于ASCII码
世界上有很多编码方案,有种方案叫做ASCII编码,是在计算机系统中通常被采用的
简单地说,所谓编码方案,就是一套规则,它约定了用什么样的信息来表示现实对象
比如说,在ASCII编码方案中,用61H表示“a”,62H表示“b”
一个文本编辑过程中,就包含着按照ASCII编码规则进行的编码和解码。
在文本编辑过程中,我们按下键盘的a键,就会在屏幕上看到“a”。这是怎样一个过程呢?
我们按下键盘的a键,这个按键的信息被送入计算机
计算机用ASCII 码的规则对其进行编码
将其转化为61H存储在内存的指定空间中;
文本编辑软件从内存中取出61H,将其送到显卡上的显存中
工作在文本模式下的显卡,用ASCII码的规则解释显存中的内容
61H被当作字符“a”,显卡驱动显示器,将字符“a” 的图像画在屏幕上
我们可以看到,显卡在处理文本信息的时候,是按照ASCII码的规则进行的
这也就是说,如果我们要想在显示器上看到“a",就要给显卡提供“a”的ASCII 码,61H
如何提供?当然是写入显存中
以字符形式给出的数据
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
上面的源程序中:
db 'unIX' 相当于 db 75H,6EH,49H,58H
“u”、 “n”、“I”、 .“X”的ASCII码分别为75H、6EH、49H、58H
db 'foRK' 相当于 db 66H,6FH, 52H, 4BH”
“f”、“o”、 “R、“K”的ASCII码分别为66H、6FH、52H、4BH
mov al, 'a' 相当于 mov al,61H ,“a” 的ASCII码为61H
mov bl, 'b' 相当于 mov al,62H ,“b” 的ASCII码为62H
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200920233001596-1057667065.png)
大小写转换问题
例题:两个字符串,第一个字符串转换为大写,第二个转换为小写
assume Cs: codesg,ds :datasg datasg segment db 'BaSiC' db 'iNfOrMaTiOn' datasg ends
分析一波
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200920233040105-1969525883.png)
大写字母的ASCII码比小写字母的 小20H
大写字母ASCII码 + 20H 就变小写字母
小写字母ASCII码 - 20H 就变大写字母
但是两个字符串中都是大小写混合
以字符串一为例 (转换为大写)
应该是只将小写字母的 ASCII码值 减少20H
大写字母的不改变
这就需要程序进行判断,然而我们现在不会
再次观察一下
会发现除第五位(从右向左数,从零开始, 或者说,是从左到右的第三个)外
其余位数的二进制数都一样
而大写字母第五位为0 ,小写字母第五位为1
可以使用or 和 and 修改第五位
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 ;将转变后的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
[ba+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] movax, [bx].200
题目:
用Debug查看内存,结果如下:
2000:1000 BE 00 06 00 00 00 .....
写出下面的程序执行后,ax、bx、cx中的内容。
mov ax, 2000H mov ds, ax mov bx, 1000H . mov ax, [bx] mov cx, [bx+1] add cx, [bx+2]
思考后看分析
分析一波:
mov ax, [bx]
[bx] = ds:bx 即指向2000:1000内存单元
但该单元是一个字节
ax是存储一个字(即两个字节)的
所以ax值为 00BE
不理解的可以参考
mov cx, [bx+1]
[bx] = ds:(bx+1) 指向2000:1001内存单元
(cx)=0600H
add cx, [bx+2]
[bx] = ds:(bx+2) 指向2000:1002内存单元
(cx)=0606H。
用[bx+idata]的方式进行数组的处理
有了[bx+idata]这种表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据
我们通过下面的问题来理解这一点。
assume cs: codesg,ds:datasg datasg segment db 'BaSiC' db 'iNfOrMaTiOn' datasg ends codesg segment start: mov ax,datasg ;让ax获取datasg段的 段地址 mov ds,ax mov bx,0 ;初始化bx mov cx,5 ;循环5次 s: mov al,[bx] and al,1101111b mov [bx],al ;定位第一个字符串中的字符 mov al,[5+bx] or al,01000110b mov [5+bx],al ;定位第二个字符串中的字符 mov al,[10+bx] or al,00100000b mov [10+bx],al ;定位第二个字符串第六个字符 inc bx loop s mov ax, 4c00h int 21h codesg ends end start
SI 和 DI 寄存器
si 和 di是8086CPU中和bx功能相近的寄存器
si和di不能够分成两个8位寄存器来使用
下面的3组指令实现了相同的功能
(1) mov bx, 0 mov ax,[bx] (2) mov si, 0 mov ax,[si] (3) mov di, 0 mov ax,[di]
下面的3组指令也实现了相同的功能
(1) mov bx, 0 mov ax, [bx+123] (2) mov si, 0 mov ax, [si+123] (3) 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 codesg segment start: mov ax,datasg mov ds,ax mov si,0 mov di,16 mov cx,8 ;因为一次性转移16位()两个字节),所以只需循环8次 s: mov ax,[si] mov [di],ax ;注意,[bx] [si] [di] 之间不能直接mov bx,si,di可以 add si,2 ;因为这两个存储器没有高低位,一次性转移16位,两个字节 add di,2 ;再加上内存单元是8个字节,所以要+2 loop s mov ax,4c00h int 21h codesg ends end start
使用更少的代码来实现同样的效果
提示,可以使用[bx(si或di) + idata] 的方式
assume cs:codesg,ds:datasg datasg segment db 'welcome to masm!' db '...................................' datasg ends 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
[bx+si] 和 [bx+di]
在前面,我们用[bx(si 或di]和[bx(si或di)+idata]的方式来指明一个内存单元
我们还可以用更为灵活的方式: [bx+si]和[bx+di]
[bx+si]和[bx+di]的含义相似,以[bx+si]为例进行讲解
[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)
mov ax,[bx+si]
将 段地址为 ds,偏移地址 为 bx + si 的内存单元中的 数据送入 ax中
数学化的描述为: (ax)=((ds)* 16+(bx)+(si))
该指令也可以写成如下格式(常用):
mov ax, [bx] [si]
例题
内存:
2000:1000 BE 00 06 00 00 00 ...
写出下面程序 执行后 ax,bx,cx 中的内容
mov ax,2000H mov ds,ax mov bx,1000H mov si,0 mov ax,[bx+si] inc si mov cx,[bx+si] inc si mov di,si add cx,[bx+di]
分析:
mov ax,2000H ax = 2000
mov ds,ax ds = 2000
mov bx,1000H bs = 1000
mov si,0 si = 0000
mov ax,[bx+si] ax = 00BE
inc si si = 0001
mov cx,[bx+si] cs = 0600
inc si si = 0002
mov di,si di = 0002
add cx,[bx+di] cx = 0606
[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
例题
内存中:
2000:1000 BE 00 06 00 6A 22 .....
写出下面程序 执行后 ax,bx,cx 中的内容
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
分析:
mov ax,2000H ax = 2000
mov ds,ax ds = 2000
mov bx,1000H bx = 1000
mov si,0 si = 0
mov ax,[bx+2+si] ax = 00BE
inc si si = 1
mov cx,[bx+2+si] cx = 6A00
inc si si = 2
mov di,si di = 2
mov bx,[bx+2+di] bx = 226A
不同寻址方式的灵活应用
如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式)
就可以发现:
(1) [idata]用一个 常量 来表示地址,可用于直接定位一个内存单元;
(2) [bx]用一个 变量 来表示内存地址,可用于间接定位一个内存单元;
(3)[bx+idata]用一个 变量 和 常量 表示地址,可在一个起始地址的基础上用 变量 间接定位一个内存单元;
(4) [bx+si]用两个 变量 表示地址;
(5) [bx+si+idata]用两个 变量 和一个 常量 表示地址。
可以看到,从[idata]一 直到[bx+si+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: codesg ends end start
答案
assume cs:codesg,ds:datasg datasg segment db '1. file.........' 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] and al,11011111b mov [bx+3],al add bx,16 loop s mov ax,4c00h int 21h codesg ends end start
例题
将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
分析:
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200920234154901-697582357.png)
我们需要进行4x3次的二重循环,用变量R定位行,变量C定位列
外层循环按行来进行,内层按列来进行
首先用R定位第1行,然后循环修改R行的前3列
然后再用R定位到下一行,再次循环修改R行的前3列.....
如此重复直到所有的数据修改完毕
处理的过程大致如下
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200920234221519-1246332441.png)
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 si,0 mov cx,3 s: mov al,[bx+si] and al,11011111b mov [bx+si],al inc si loop s add bx, 16 loop s0 mov ax,4c00h int 21h codesg ends end start
仔细阅读上面的程序,看看有什么问题?
思考后看分析
分析:
问题在于cx的使用,我们进行二重循环,却只用了一个循环计数器
造成在进行内层循环的时候,覆盖了外层循环的循环计数值
多用-一个计数器又不可能,因为loop 指令默认cx为循环计数器。怎么办呢?
我们应该在每次开始内层循环的时候,将外层循环的cx中的数值保存起来
在执行外层循环的loop指令前,再恢复外层循环的cx数值
可以用寄存器dx来临时保存cx中的数值,改进的程序如下
assume cs:codesg,ds:datasg datasg segment db '1. file.........' 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,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 mov ax,4c00h int 21h codesg ends end start
上面的程序用dx来暂时存放cx中的值
如果在内层循环中,dx寄存器也被使用,该怎么办?我
们似乎可以使用别的寄存器,但是CPU中的寄存器数量毕竟是有限的,如8086CPU只有14个寄存器
在上面的程序中,si、 cx、ax、bx,显然不能用来暂存cx中的值,因为这些寄存器在循环中也要使用;
cs、 ip、 ds 也不能用,因为cs:ip 时刻指向当前指令,ds 指向datasg 段;
可用的就只有: dx、 di、 es、 ss、 sp、bp等6个寄存器了。
可是如果循环中的程序比较复杂,这些寄存器也都被使用的话,那么该如何?
我们在这里讨论的问题是,程序中经常需要进行数据的暂存,怎样做将更为合理。
这些数据可能是寄存器中的,也可能是内存中的。
我们可以用寄存器暂存它们,但是寄存器的数量有限,每个程序中可使用的寄存器都不一样
我们希望寻找一个通用的方案,来解决这种在编程中经常会出现的问题
显然,我们不能选择寄存器,那么可以使用的就是内存了。
可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复
这样我们就需要开辟一段内存空间
再次改进的程序如下
assume cs:codesg,ds:datasg datasg segment db '1. file.........' db '2. edit.........' db '3. search.......' db '4. view.........' db '5. options......' db '6. help.........' datasg ends stack segment dw 0,0,0,0,0,0,0,0 stack 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
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200920234342398-1148293514.png)
(076c:0000那一行是栈)
参考: 王爽 - 汇编语言 和 小甲鱼零基础汇编