and和or指令
and 指令:逻辑与指令,按位进行与运算。
通过该指令可将操作对象的相应位设为0,其他位不变。
or指令:逻辑或指令,按位进行或运算。
通过该指令可将操作对象的相应位设为1,其他位不变。
ASCII码
信息存储在计算机中,要对其进行编码,将其转化为二进制信息进行存储。而计算机要将这些存储的信息再显示给我们看,就要再对其进行解码。只要编码和解码采用同样的规则,就可以将人能理解的信息存入到计算机,再从计算机中取出。
世界上有很多编码方案,有一种方案叫做ASCII编码,是在计算机系统中通常被采用的。简单地说,所谓编码方案,就是一套规则,它约定了用什么样的信息来表示现实对象。比如说,在ASCII编码方案中,用61H表示“a",62H表示"b"。一种规则需要人们遵守才有意义。
以字符形式给出的数据
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"的ASCll码分别为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。
用r命令分析一下,因为“ds=075A”,所以程序从076A开始。cs为076B是指令执行的入口(end start决定),代码段的起始位置为076A,查看下数据即可。
大小写转换
字符串“BaSiC",对其中的小写字母所对应的ASCII码进行减20H的处理,将其转为大写,对其中的大写字母不进行改变;字符串"iNtDrMaTiOn", 对其中的大写字母所对应的ASCII码进行加20H 的处理,将其转为小写,而对千其中的小写字母不进行改变。
可以看出来,小写字母的ASCII码值比大写字母的ASCII码值大20H。这样,我们可以想到,如果将“a"的ASCII码值减去20H,就可以得到“A”;如果将“A"的ASCII码值加上20H 就可以得到“a"。
必须判断是大写字母还是小写字母,才能决定进行何种处理,而现在又没有可以使用的用于判断的指令。
重新观察,寻找新的规律。可以看出,就ASCII 码的二进制形式来看,除第5位(位数从0开始计算)外,大写字母和小写字母的其他各位都一样。大写字母ASCII码的第5位为0, 小写字母的第5位为1。这样,我们就有了新的方法,一个字母,不管它原来是大写还是小写,将它的第5位置0,它就必将变为大写字母:将它的第5 位置1,它就必将变为小写字母。在这个方法中,我们不需要在处理前判断字母的大小写。比如:对于“BaSiC"中的“ B"按要求,它已经是大写字母了,不应进行改变,将它的第5位设为0,它还是大写字母,因为它的第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 #将转变后的ASCII码写回原单元 inc bx #(bx)加1,ds:bx指向下一个字母 loop s mov bx,5 #设置(bx)=5,ds:bx指向'iNfOrMatiOn'的第一个字母 mov cx,11 #设置循环次数11,因为'iNfOrMaTiOn'有十一个字母 s0:mov ax,[bx] or al,00100000B #将al中的ASCII码的第5位置设为1,变为小写字母 mov [bx],al inc bx loop s0 mov ax,4c00h int 21h codesg ends end start
[bx+idata]
用[bx]的方式来指明一个内存单元,还可以用一种更为灵活的方式来指明内存单元:[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata) 。
mov ax,[bx+200] 的含义:
将一个内存单元的内容送入ax, 这个内存单元的长度为2 个字节(字单元),存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。
数学化的描述为: (ax)=((ds)*l6+(bx)+200)
该指令也可以写成如下格式(常用):
mov ax, [200+bx]
mov ax,200[bx]
mov ax, [bx].200
用[bx+idata]的方式进行数组的处理
将datasg中定义的第一个字符串转化为大写,第二个字符串转化为小写。
assume cs:codesg,ds:datasg datasg segment db 'BaSIC' db 'MinIX' datasg ends codesg segment start: codesg ends end start
有了[bx+idata]这种表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据。观察datasg 段中的两个字符串,一个的起始地址为0,另一个的起始地址为5。我们可以将这两个字符串看作两个数组,一个从0地址开始存放,另一个从5开始存放。那么我们可以用[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符。
assume cs:codesg,ds:datasg datasg segment db 'BaSIC' db 'MinIX' datasg ends codesg segment start:mov ax,datasg mov ds,ax #设置ds的段地址为datasg的地址 mov bx,0 #设置偏移量 mov cx,5 s:mov al,[bx] #定位第一个字符串中的字符 and al,11011111b #第五位变为0,即转为大写 mov [bx],al mov al,[5+bx] #定位第二个字符串中的字符 or al,00100000b #第五位转为1,即转为小写 mov [5+bx],al inc bx loop s codesg ends end start
也可以写成
assume cs:codesg,ds:datasg datasg segment db 'BaSIC' db 'MinIX' datasg ends codesg segment start:mov ax,datasg mov ds,ax #设置ds的段地址为datasg的地址 mov bx,0 #设置偏移量 mov cx,5 s:mov al,0[bx] #定位第一个字符串中的字符,0代表第一个数组的起始位置 and al,11011111b #第五位变为0,即转为大写 mov 0[bx],al mov al,5[bx] #定位第二个字符串中的字符,5代表第二个数组的起始位置 or al,00100000b #第五位转为1,即转为小写 mov 5[bx],al inc bx loop s codesg ends end start
C语言
char a[5]="BaSiC"; char b[5]="MinIX"; main() { int i; i=0; do { a[i]=a[i]&0xDF; b[i]=b[i]|10x20; i++; } while(i<5); }
c语言和汇编语言的相似之处,数组名称就相当于起始地址。
- C 语言: a[i],b[i]
- 汇编语言: 0[bx],5[bx]
[bx+idata]的方式为高级语言实现数组提供了便利机制。
SI和DI
si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。
下面三组实现相同功能:
下面三组实现相同功能:
用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 #设置ds地址段 mov si,0 #设置偏移量 mov di,16 #设置第二个数组的偏移量 mov cx,8 #循环八次,因为一个字母一个字节,一次可以读两个字节 s:mov ax,[si] #将第一个数组的内容移入ax mov [di],ax #将ax赋值给第二个数组的相同位置 add si,2 #数组增量为2 add di,2 #增量为2 loops mov ax,4c00h int 21h codesg ends end start
在程序中,用16位寄存器进行内存单元之间的数据传送,一次复制2个字节,一共循环8次。
改进
assume cs:codesg,ds:datasg datasg segment db 'welcome to masm !' db '................' datasg ends codesg segment start: mov ax,datasg mov ds,ax #设置ds地址段 mov si,O #设置偏移量 mov cx,8 #循环八次,因为一个字母一个字节,一次可以读两个字节 s:mov ax,0[si] #将第一个数组的内容移入ax mov 16[si],ax #将ax赋值给第二个数组的相同位置 add si,2 #增量为2 loops 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)+(si)(即bx中的数值加上si 中的数值)。
指令mov ax,[bx+si] 的含义如下:
将一个内存单元的内容送入ax,这个内存单元的长度为2 字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中。
数学化的描述为: (ax)=((ds)*16+(bx)+(si))
该指令也可以写成如下格式(常用):
mov ax,[bx][si]
[bx+si+idata]和[bx+di+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
不同的寻址方式的灵活应用
(1) [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
(2) [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
(3) [bx+data]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
(4) [bx+si]用两个变量表示地址;
(5) [bx+si+idata]用两个变量和一个常量表示地址。
多层循环嵌套
用dx实现多层循环
assume cs:codesg,ds:datasg datasg segment db 'ibm ' #十六个字符,占用16个字节,不足的用空格补到16个 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 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 #加16到下一个字母,因为每个db占用16个字节 mov cx,dx #将dx存储的外层循环恢复到cx中 loop s0 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 'ibm ' #十六个字符,占用16个字节,不足的用空格补到16个 db 'dec ' db 'dos ' db 'vax ' dw 0 #前面每个站16字节,所以这个空间偏移量为40H datasg ends codesg segment start:mov ax,datasg mov ds,ax #设置数据段地址 mov bx,0 #设置偏移量 mov cx,4 s0:mov ds:[40H],cx #将外层循环的cx值保存在ds:[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 #加16到下一个字母,因为每个db占用16个字节 mov cx,ds:[40H] #将datasg:40H单元存储的外层循环恢复到cx中 loop s0 codesg ends end start
一般来说,在需要暂存数据的时候,都应该使用栈。
使用栈实现多层循环
assume cs:codesg,ds:datasg,ss:stacksg datasg segment db 'ibm ' #十六个字符,占用16个字节,不足的用空格补到16个 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 #加16到下一个字母,因为每个db占用16个字节 pop cx #从栈顶弹出原cx的值,恢复cx loop s0 #外层循环的loop指令将cx中的计数值减一 codesg ends end start