and和or指令

and:逻辑与指令,按位与运算,如:

  1. mov al,01100011B
  2. and al,00111011B

执行结果是al=00100011B,所以我们想要把某一位置零的时候可以使用and指令。
or:逻辑或指令,按位或运算,如:

  1. mov al,01100011B
  2. or al,00111011B

执行结果是al=01111011B,or指令可以将相应位置1。

ASCII码和字符形式的数据

在汇编语言中我们可以使用'…'的方式指明数据是以字符形式给出的,编译器会自动将它们转化为ASCII码。例如:

  1. assume cs:code,ds:data
  2. data segment
  3. db 'unIX'
  4. db 'foRK'
  5. data ends
  6. code segment
  7. start:mov al,'a'
  8. mov bl,'b'
  9. mov ax,4c00h
  10. int 21h
  11. code ends
  12. end start

db和dw类似,只不过定义的是字节型数据,然后通过 ‘unIX’ 相继在接下来四个字节中写下75H,6EH,49H,58H即unIX的ASCII值。同理,mov al,'a'也是将 ‘a’ 的ASCII值61H送入al寄存器。

使用and和or指令改变一串字符串字母的大小写,将第一串全变为大写,第二串全变为小写:

首先分析ASCII码:

  1. 大写 十六进制 二进制 小写 十六进制 二进制
  2. A 41 01000001 a 61 01100001
  3. B 42 01000010 b 62 01100010
  4. C 43 01000011 c 63 01100011

可见,只有第5位(从右往左数,从0开始计数)在大写和小写的二进制中是不一样的,所以我们只要把所有字母的二进制第五位置零,那就是大写,置1就是小写。代码如下:

  1. assume cs:codesg,ds:datasg
  2. datasg segment
  3. db 'BaSiC'
  4. db 'iNfOrMaTiOn'
  5. datasg ends
  6. codesg segment
  7. start:mov ax,datasg
  8. mov ds,ax
  9. mov bx,0
  10. mov cx,5
  11. s:mov al,[bx]
  12. and al,11011111B
  13. mov [bx],al
  14. inc bx
  15. loop s
  16. mov bx,5
  17. mov cx,11
  18. s0:mov al,[bx]
  19. or al,00100000B
  20. mov [bx],al
  21. inc bx
  22. loop s0
  23. mov ax,4c00h
  24. int 21h
  25. codesg ends
  26. end start

物理地址寻址方式

回忆一下寄存器一章当中讲过的物理地址寻址方式

段地址:偏移地址的方式来寻址

物理地址=段地址*16+偏移地址

段地址一般保存在段寄存器中(对于内存中数据的访问,其段地址保存于DS段寄存器中,[……]表示一个内存单元,……为数据的偏移地址)

例如访问10000H处的内存单元,可让DS=1000,并让偏移地址为0

  1. mov bx,1000H
  2. mov ds,bx
  3. 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

  1. mov ax,[200+bx]
  2. mov ax,200[bx]
  3. mov ax,bx.200
  4. ;以上三句都与mov ax,[bx+200]同义

既然有了这种表示方法,我们就可以使用这种方法来操作数组,刚才将两个字符串改变大小写的代码的循环部分可以如下优化:

  1. ···
  2. s:mov al,[bx]
  3. and al,11011111B
  4. mov [bx],al
  5. mov al,[5+bx]
  6. or al,00100000B
  7. mov [5+bx],al
  8. inc bx
  9. loop s
  10. ···

当然也可写为0[bx]和5[bx],注意这种写法和C语言中数组的相似之处:C语言中数组表示为a[i],汇编语言中表示为5[bx]。

SI和DI寄存器

常用的寄存器除了前面提到的以外,还有si和di,这两个寄存器不能被分成两个独立8位寄存器的。从名字上来看应该是source index和destination index,主要用来做数据转移时记录index偏移

SI和DI功能和BX相似,但不可以拆分为两个8位寄存器。也就是说下面代码等价:

  1. mov bx|si|di,0
  2. mov ax,[bx|si|di]
  3. 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],相似的,也可以写成

  1. mov ax,[bx+200+si]
  2. mov ax,[200+bx+si]
  3. mov ax,200[bx][si]
  4. mov ax,[bx].200[si]
  5. mov ax,[bx][si].200

总结

那我们总结一下这些内存寻址方法:

  • [idata] 用一个常量来表示地址,可用于直接定位一个内存单元
  • [bx] 用一个变量表示内存地址,可用于间接定位一个内存单元
  • [bx+idata] 用一个常量和一个变量表示偏移地址,可在一个起始地址的基础上间接定位一个内存单元
  • [bx+si] 用两个变量表示偏移地址
  • [bx+si+idata] 用两个变量和一个常量表示偏移地址

使用双循环,使用一个寄存器暂存cs的值,如:

  1. ···
  2. mov cx,4
  3. s0:mov dx,cx
  4. mov si,0
  5. mov cx,3
  6. s:mov al,[bx+si]
  7. and al,11011111b
  8. mov [bx+si],al
  9. inc si
  10. loop s
  11. add bx,16
  12. mov cx,dx
  13. loop s0
  14. ···

假如循环比较复杂,没有多余的寄存器可用,我们可以使用内存暂存cx或其他数据:

  1. ···
  2. dw 0
  3. ···
  4. mov cx,4
  5. s0:mov ds:[40H],cx
  6. mov si,0
  7. mov cx,3
  8. s:mov al,[bx+si]
  9. and al,11011111b
  10. mov [bx+si],al
  11. inc si
  12. loop s
  13. add bx,16
  14. mov cx,ds:[40H]
  15. loop s0
  16. ···

这么使用的话注意需要在数据段声明用来暂存的内存,好在程序加载时分配出来。当然,在需要暂存的地方,还是建议使用栈:

  1. ···
  2. dw 0,0,0,0,0,0,0,0
  3. ···
  4. mov ax,stacksg
  5. mov ss,ax
  6. mov sp,16
  7. ···
  8. mov cx,4
  9. s0:push cx
  10. mov si,0
  11. mov cx,3
  12. s:mov al,[bx+si]
  13. and al,11011111b
  14. mov [bx+si],al
  15. inc si
  16. loop s
  17. add bx,16
  18. pop cx
  19. loop s0
  20. ···