学习参考:汇编语言(第2版)王爽
- and 和 or 指令
- SI 和 DI
- [bx+si] 和 [bx+di]
- [bx+si+idata] 和 [bx+di+idata]
- 不同的寻址方式的灵活应用
and 和 or 指令
1.and 指令:逻辑与命令,按位进行与运算
1 | mov al,01100011B |
执行后:al=00100011B
通过该指令可将操作对象的响应位设为 0,其他位不变
1 | and al,10111111B ; 将 al 的第 6 位设为 0 |
2.or 指令:逻辑或指令,按位进行或运算
1 | mov al,01100011B |
执行后:al=01111011B
通过该指令可将操作对象的响应位设为 1,其他位不变
1 | or al,01000000B ; 将 al 的第 6 位设为 1 |
这里为了用已经学到的指令来完成任务,所以我们还需要进行一些分析,下面是 ASCII 表的一部分
经过观察可以发现,大写字母与小写字母的 ASCII 码的 16 进制数差 20H,(十进制差 32),然后再观察二进制数,除第 5 位(位数从 0 开始计算)外,大写字母和小写字母的其他各位都一样的。所以大写字母要变成小写字母,只需要让第 5 位变 1 即可,小写字母要变成大写字母,只需要让第 5 位变 0 即可。那么如何变呢?当然是用刚学过的 or 和 and 指令。
完整程序如下:
1 | assume cs:codesg,ds:datasg |
现在,我们有了 [bx+idata] 的方式,就可以用更渐变的方法来完成上面的程序。观察datasg 段中的两个字符串,一个的起始地址为0,另一个的起始地址为5。我们可以将这两个字符串看作两个数组,一个从0地址开始存放,另一个从5 开始存放。那么我们可以用[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符。在这里,0 和5 给定了两个字符串的起始偏移地址,bx 中给出了从起始偏移地址开始的相对地址。这两个字符串在内存中的起始地址是不一样的,但是,它们中的每一个字符,从起始地址开始的相对地址的变化是相同的。改进的程序如下.
1 | mov bx,0 |
程序也可以写成下面的样子:
1 | mov bx,0 |
便于理解,用 C 语言来描述上面的程序,大致程序如下:
1 |
|
SI 和 DI
si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,si 和 di 不能分成两个 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))
该指令也可以写成如下格式(常用):
1 | mov ax,[bx][si] |
[bx+si+idata] 和 [bx+di+idata]
[bx+si+idata] 和 [bx+di+idata] 的含义相似,我们以 [bx+si] 为例讲解
[bx+si+idata] 表示一个内存单元,它的偏移地址为 (bx)+(si)+idata(即 bx 中的数值加上 si 中的数值)
指令 mov ax,[bx+si]
的含义如下:
将一个内存单元的内容送入 ax,这个内存单元的长度为 2 字节(字单元),存放一个字,偏移地址为 bx 中的数值加上 si 中的数值再加上 idata,段地址在 ds 中。
数字化描述为:(ax)=((ds)*16+(bx)+(si)+idata)
该指令也可以写成如下格式(常用):
1 | mov ax,[bx+200+si] |
不同的寻址方式的灵活应用
通过比较前面用到的几种定位内存地址的方法(可称为寻址方式),可以发现:
- [idata] 用一个常量来表示地址,可用于直接定位一个内存单元
- [bx] 用一个变量来表示内存地址,可用于间接定位一个内存单元
- [bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元
- [bx+si] 用两个变量表示地址
- [bx+si+idata] 用两个变量和一个常量表示地址
Q: 编程,将 datasg 段中每个单词改写为大写字母
1 | assume cs:codesg,ds:datasg |
A: 类似这种问题,我们最自然能想到的就是使用二重循环来实现,那么在汇编中如何实现二重循环呢?
如果要使用二重循环的话,那么思路应该是这样的:
- 首先定义外层循环,用来扫描每一行
- 接着定义内存循环,用来扫描每一列
- 将访问到的数据取出,做位与运算
处理过程大致如下:
1 | r=第一行地址 |
那么,再次根据这个大致过程,我们再来分析一下,我们知道,要使用 loop ,那么就需要用 cx 来保存循环次数,可是这里就有一个矛盾的地方,如果 cx 记录了外层循环的次数,那么内层循环怎么办?或者说记录内层的循环次数,外层的又怎么办?
这里可以提供一个思路,我们或许可以用寄存器,来暂时保存 cx 的值,比如我是用 dx 来暂时保存外层循环的次数,当内层循环执行完过后,再将 dx 的值还给 cx。可是这样有一个问题,如果 dx 在内部被使用了怎么办?我们只有 14 个寄存器,数量有限,显然这种方式在某些情况下不合适
那么再提供一个思路,我们能否在数据段内定义一个块内存单元,用来保存外层 cx 的值,比如再进行内层循环之前,执行这种操作 mov ds:[40H],cx
,需要使用的时候再从内存单元中恢复 `mov cx,ds:[40H]。似乎也可行,但是这种做法却很麻烦,如果你需要保存多个数据的话,那么就需要记住数据放到了哪个单元,这样程序容易混乱。
综合上述分析过后,一般来说,在需要暂存数据的时候,我们都应该使用栈
那么,再次改进我们的程序
1 | assume cs:codesg,ds:datasg,ss:stacksg |