and和or指令
and:逻辑与指令,按位与运算,如:
mov al,01100011B
and al,00111011B
执行结果是al=00100011B,所以我们想要把某一位置零的时候可以使用and指令。
or:逻辑或指令,按位或运算,如:
mov al,01100011B
or al,00111011B
执行结果是al=01111011B,or指令可以将相应位置1。
ASCII码和字符形式的数据
在汇编语言中我们可以使用'…'
的方式指明数据是以字符形式给出的,编译器会自动将它们转化为ASCII码。例如:
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和dw类似,只不过定义的是字节型数据,然后通过 ‘unIX’ 相继在接下来四个字节中写下75H,6EH,49H,58H即unIX的ASCII值。同理,mov al,'a'
也是将 ‘a’ 的ASCII值61H送入al寄存器。
使用and和or指令改变一串字符串字母的大小写,将第一串全变为大写,第二串全变为小写:
首先分析ASCII码:
大写 十六进制 二进制 小写 十六进制 二进制
A 41 01000001 a 61 01100001
B 42 01000010 b 62 01100010
C 43 01000011 c 63 01100011
可见,只有第5位(从右往左数,从0开始计数)在大写和小写的二进制中是不一样的,所以我们只要把所有字母的二进制第五位置零,那就是大写,置1就是小写。代码如下:
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]
and al,11011111B
mov [bx],al
inc bx
loop s
mov bx,5
mov cx,11
s0:mov al,[bx]
or al,00100000B
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
物理地址寻址方式
回忆一下寄存器一章当中讲过的物理地址寻址方式。
用段地址:偏移地址的方式来寻址
物理地址=段地址*16+偏移地址
段地址一般保存在段寄存器中(对于内存中数据的访问,其段地址保存于DS段寄存器中,[……]表示一个内存单元,……为数据的偏移地址)
例如访问10000H处的内存单元,可让DS=1000,并让偏移地址为0
mov bx,1000H
mov ds,bx
mov al,[0]
因为al为八位寄存器,所以以上代码表示将地址10000H处一个字节的数据存入al寄存器中
若将al改为ax十六位寄存器,则表示将地址10000H及10001H处两个字节的数据存入ax寄存器中
[address]或[bx]
除了上文直接用[address]的方式寻址,也可以用[bx]的方式,表示bx寄存器中的数值作为偏移地址。详见
[bx+idata]的内存表示方法与数组处理
除了使用[bx]来表示一个内存单元外,我们还可以使用[bx+idata]
来表示一个内存单元,他表示的意思是偏移地址为(bx)+idata(bx中的数值加idata)的内存单元。当然也可写为[idata+bx]
,除此之外还可写为,200[bx]
,[bx].200
。
mov ax,[200+bx]
mov ax,200[bx]
mov ax,bx.200
;以上三句都与mov ax,[bx+200]同义
既然有了这种表示方法,我们就可以使用这种方法来操作数组,刚才将两个字符串改变大小写的代码的循环部分可以如下优化:
···
s:mov al,[bx]
and al,11011111B
mov [bx],al
mov al,[5+bx]
or al,00100000B
mov [5+bx],al
inc bx
loop s
···
当然也可写为0[bx]和5[bx],注意这种写法和C语言中数组的相似之处:C语言中数组表示为a[i],汇编语言中表示为5[bx]。
SI和DI寄存器
常用的寄存器除了前面提到的以外,还有si和di,这两个寄存器不能被分成两个独立8位寄存器的。从名字上来看应该是source index和destination index,主要用来做数据转移时记录index偏移
SI和DI功能和BX相似,但不可以拆分为两个8位寄存器。也就是说下面代码等价:
mov bx|si|di,0
mov ax,[bx|si|di]
mov ax,[bx|si|di+123]
所以在这里可以使用更方便的方式:[bx+si]和[bx+di],这两个式子表示偏移地址为(bx)+(si)的内存单元,使用方法如:mov ax,[bx+si]
等价于mov ax,[bx][si]
当然,有了这些表示方法,自然就有[bx+si+idata]
和[bx+di+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
总结
那我们总结一下这些内存寻址方法:
- [idata] 用一个常量来表示地址,可用于直接定位一个内存单元
- [bx] 用一个变量表示内存地址,可用于间接定位一个内存单元
- [bx+idata] 用一个常量和一个变量表示偏移地址,可在一个起始地址的基础上间接定位一个内存单元
- [bx+si] 用两个变量表示偏移地址
- [bx+si+idata] 用两个变量和一个常量表示偏移地址
使用双循环,使用一个寄存器暂存cs的值,如:
···
mov cx,4
s0:mov dx,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
mov cx,dx
loop s0
···
假如循环比较复杂,没有多余的寄存器可用,我们可以使用内存暂存cx或其他数据:
···
dw 0
···
mov cx,4
s0:mov ds:[40H],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
mov cx,ds:[40H]
loop s0
···
这么使用的话注意需要在数据段声明用来暂存的内存,好在程序加载时分配出来。当然,在需要暂存的地方,还是建议使用栈:
···
dw 0,0,0,0,0,0,0,0
···
mov ax,stacksg
mov ss,ax
mov sp,16
···
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
···