4. 更灵活的定位内存地址的方法、数据处理的两个基本问题

1. and和or指令

  • and指令,逻辑与指令,and al, 11110111B,可将操作对象的相应位设为0,其它位不变
  • or指令,逻辑或指令,or al, 00001100B,可将操作对象的相应位设为1,其它位不变

2. 以字符形式给出的数据

我们可以在汇编程序中,用'.......'的方式指明数据是以字符的形式给出的,编译器把它们转化为相对应的ASCII码。如下程序:

sql 复制代码
 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

因'DS = 075CH',所以程序从076C段开始,而data段是程序中第一个段,它就在程序的起始处,所以它的段地址为076C

3. 大小写转换问题

将datasg中的第一个字符串转化为大写,第二个字符串转化为小写。

arduino 复制代码
 datasg segment
     db 'Basic'
     db 'iNfOrMaTiOn'
 datasg ends

以字符'a'为例,它的ASCII码是61H,大写字符'A'的ASCII码为41H。我们可知大写字符的ASCII码 + 20H = 小写字符的ASCII码。但前提是我们先判断这个字符是大写还是小写。从另外一个角度出发,小写字符的ASCII码的二进制编码的第六位总是1。

大写 二进制 小写 二进制

A 01000001 a 01100001

我们可以利用逻辑与或指令改变它的编码,这样就无需判断它原本是大写或者小写。

arduino 复制代码
 assume cs:code, ds:data
 ​
 data segment
     db 'BaSiC'
     db 'iNfOrMaTiOn'
 data ends
 ​
 code segment
 start: mov ax, data
        mov ds, ax
 ​
        mov bx, 0; 设置bx = 0,ds:bx指向'BaSiC'第一个字母
        mov cx, 5; 循环五次
   
   s0:  mov al, [bx]
        and al, 11011111B
        mov [bx], al
        inc bx
        loop s0
 ​
        mov bx, 5
        mov cx, 11
 ​
 s1:    mov al, [bx]
        or al, 00100000B
        mov [bx], al
        inc bx
        loop s1
        
        mov ax, 4c00H
        int 21H
 code ends
 ​
 end start

4. [bx + idata]

在前面,我们用[bx]的方式来指明一个内存单元,还可以用一种更为灵活的方式来指明内存单元: [bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata

有了[bx+idata]这种表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据。在codesg中填写代码,将datasg中定义的第一个字符串转化为大写,第二个字符串转化为小写。问题如下:

kotlin 复制代码
 assume cs:code, ds:data
 ​
 data segment
   db 'BaSiC'
   db 'MinIX'
 data ends
 ​
 code segment
   start:
 code ends
 ​
 end start

按照原来的方法,用[bx]的方式定位字符串中的字符。代码段中的程序如下。

ini 复制代码
 code segment
 start: mov ax, data
        mov ds, ax
 ​
        mov bx, 0; 设置bx = 0,ds:bx指向'BaSiC'第一个字母
        mov cx, 5; 循环五次
   
   s0:  mov al, [bx]
        and al, 11011111B
        mov [bx], al
        inc bx
        loop s0
 ​
        mov bx, 5
        mov cx, 5
 ​
 s1:    mov al, [bx]
        or al, 00100000B
        mov [bx], al
        inc bx
        loop s1
        
        mov ax, 4c00H
        int 21H
 code ends

观察以上程序可知,两个字符串的起始地址不一样,一个起始地址是0,另一个起始地址是5。我们可以将字符串看作两个数组,一个从0地址开始存放,另一个从5开始存放。那么,我们可以用[0+bx]和[5+bx]的方式在同一个循环里定位这两个字符串的字符。

less 复制代码
 assume cs:code, ds:data
 ​
 data segment
   db 'BaSiC'
   db 'MinIX'
 data ends
 ​
 code segment
 start: mov ax, data
        mov ds, ax
        mov bx, 0
        mov cx, 5
 ​
   s:   mov al, [bx]
        mov ah, [5+bx]
        and al, 11011111B
        or ah, 00100000B
        mov [bx], al
        mov [5+bx], ah
        inc bx
        loop s
 ​
        mov ax, 4c00H
        int 21H
 code ends
 ​
 end start
 ​
 ​

5. SI和DI

SI和DI是8086CPU中和bx中功能相近的寄存器,但是它不能够分成两个8位寄存器使用。

问题:用SI和DI实现将字符串'welcome to masm!'复制到它后面的数据区中。

sql 复制代码
 assume cs:code, ds:data
 ​
 data segment
   db 'welcome to masm!'
   db '................'
 data ends
 ​
 code segment
   start:  mov ax, data
         mov ds, ax
         mov si, 0
         mov cx, 8; 可以按字来复制,减少循环次数
 ​
     s:  mov ax, [si]
         mov [16+si], ax
         add si, 2
         loop s
         
        mov ax, 4c00H
        int 21H
 code ends
 ​
 end start

6. 不同的寻址方式的灵活应用

  • idata\]用一个常量来表示地址,可用于直接定位一个内存单元。

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

css 复制代码
 mov ax, [200+bx+si]
 mov ax, [bx+200+si]
 mov ax, 200[bx][si]
 mov ax, [bx].200[si]
 mov ax, [bx][si].200

将data段中的每个单词的头一个字母改为大写字母。

kotlin 复制代码
data segment
  db '1. file         '
  db '2. edit         '
  db '3. search       '
  db '4. view         '
  db '5. options      '
  db '6. help         '
data ends
sql 复制代码
assume cs:code, ds:data

data segment
  db '1. file         ';每个字符串的长度都是16个字节
  db '2. edit         '
  db '3. search       '
  db '4. view         '
  db '5. options      '
  db '6. help         '
data ends

code segment
  start:  mov ax, data
	    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
code ends

end start

问题2:将data段中每个单词改为大写字母

kotlin 复制代码
data segment
  db 'ibm             '
  db 'dec             '
  db 'dos             '
  db 'vax             '
data ends

每个字符串的长度都是16个字符,这里很明显有两个循环变量,一个用来定位行,一个用来定位列。

ini 复制代码
    mov ax, data
    mov ds, ax
    mov bx, 0; 用bx来定位行
    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中的值恢复外层循环的计数
	loop s0

但寄存器的数量毕竟是有限的,有的时候会出现寄存器不够的情况,这个时候我们可以在再开辟一个内存来储存相应的计数值。但这样做毕竟比较繁琐。当我们需要保存多个数据的时候,我们必须记住数据放到了哪个单元,这样程序容易混乱。一般来说,在需要暂存数据的时候,我们都应该使用栈,从而使得我们的程序更加清晰。

arduino 复制代码
assume cs:code, ds:data, ss:stack

data segment
  db 'ibm             '
  db 'dec             '
  db 'dos             '
  db 'vax             '
data ends

stack segment
  dw 0, 0, 0, 0, 0, 0, 0, 0
stack ends

code segment
  start: mov ax, stack
	   mov ss, ax
	   mov sp, 50H
	   mov ax, data
	   mov ds, ax

	   mov bx, 0
	   mov cx, 4; 外循环4次

  s0:          push cx   ;保存外循环次数
	   mov cx, 3
 	   mov si, 0
 s1:     mov al, [bx+si]
	   and al, 11011111B; 切记不要把and写成add
	   mov [bx+si], al
	   inc si
	   loop s1

	   add bx, 16
	   pop cx
	   loop s0
		
	   mov ax, 4c00H
	   int 21H   
code ends

end start

*将data段中每个单词的前4个字母改为大写字母。 *编写程序如下:

arduino 复制代码
assume cs:code, ds:data, ss:stack

data segment
  db '1. display      '
  db '2. brows        '
  db '3. replace      '
  db '4. modify       '
data ends

stack segment
  dw 0, 0, 0, 0, 0, 0, 0, 0
stack ends

code segment
  start: mov ax, stack
	   mov ss, ax
	   mov sp, 50H
	   mov ax, data
	   mov ds, ax

	   mov bx, 0
	   mov cx, 4; 外循环4次

  s0:          push cx   ;保存外循环次数
	   mov cx, 4
 	   mov si, 3
 s1:     mov al, [bx+si]
	   and al, 11011111B
	   mov [bx+si], al
	   inc si
	   loop s1

	   add bx, 16
	   pop cx
	   loop s0
		
	   mov ax, 4c00H
	   int 21H   
code ends

end start

本章总结:

  • 不同寻址方式的应用和意义
  • 二重循环问题的处理
  • 栈的应用
  • 大小写转化的方法
  • and、or指令

7. bx、si、di和bp

  • 只有这四个寄存器可以用在"[....]"中进行内存单元的寻址。
  • 这四种寄存器可以单独出现,或只能以四种组合方式出现,如[bx+si],[bx+di],[bp+si],[bp+di]。
  • 只要在[...]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。

8. 机器指令处理的数据在什么地方

指令在执行前,所要处理的数据可以在3个地方:CPU内部(包括寄存器和指令缓冲器)、内存、端口。

9. 指令处理的数据有多长

8086CPU可以处理两种尺寸的数据,btye和word。所以在机器指令中要指明进行的是字操作还是字节操作。

  1. 通过寄存器名指明要处理的数据的尺寸。
  2. 在没有寄存器名的情况下,用操作符X ptr指明内存单元的长度,这里的X可以为word和byte。

下面的指令中,用word ptr指令了指令访问的内存单元是一个字单元。

arduino 复制代码
mov word ptr ds:[0], 1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx], 2

下面的指令中,用byte ptr指令了指令访问的内存单元是一个字节单元。

csharp 复制代码
mov byte ptr ds:[0], 1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx], 2

在没有寄存器参与的内存单元访问指令中,用word ptr和byte ptr显性地指令所要访问的内存单元的长度是很必要的。

  1. 有些指令默认了访问的是字单元还是字节单元,比如,push [1000H] 就不用指明访问的是字单元还是字节单元,因为push指令只进行字操作。

10. 寻址方式的综合应用

关于DEC公司的一条记录如下。

公司名称: DEC

总裁姓名: Ken Olsen

排名: 137

收入: 40

著名产品: PDP

它在内存中的位置如表所示:

在内存中起始地址 存放数据
seg:60 +00 'DEC'
+03 'Ken Oslen'
+0C 137
+0E 40
+10 'PDP'

当我们需要修改它的数据如下:

排名: 38

收入: 增加70

著名产品:'VAX'

arduino 复制代码
mov ax, seg
mov ds, ax
mov bx, 60H		;确定记录地址,ds:bx

mov word ptr [bx+0cH], 38
add word ptr [bx+0eH], 70

mov si, 0		;用si来定位产品字符串
mov byte ptr [bx+10H+si], 'V'
inc si
mov byte ptr [bx+10H+si], 'A'
inc si
mov byte ptr [bx+10H+si], 'X'

11. div指令

div是除法指令,格式:div reg/ div 内存。并且它是单操作数指令,被除数默认在寄存器AX或AX与DX中。

如果除数是8位,被除数则为16位,默认在AX中存放

如果除数是16位,被除数则为32位,默认在DX和AX中存放,DX存放高位,AX存放低位

结果:如果除数是8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数是16位,则AX存储除法操作的商,DX存储除法操作的余数。

问题:利用除法指令计算100001/100的值

因为100001大于65535,所以它被除数是32位,100001转为16进制为186A1H

css 复制代码
mov dx, 1
mov ax, 86A1H
mov bx, 100
div bx

12. 伪指令dd和dup

db和dw分别用来定义字节型数据和字型数据。dd用来定义双字节数据。比如:

kotlin 复制代码
data segment
  db 1
  dw 1
  dd 1
data ends

上述语句定义了3个数据:

第一个数据为01H,

第二个数据为0001H,

第三个数据为00000001H。

dup是一个操作符,在汇编语言中通db、dw、dd一样,也是由编译器识别处理的符号。用来进行数据的重复。比如

scss 复制代码
db 3 dup (0)

定义了三个字节,值都是0,相当于db 0, 0, 0

scss 复制代码
db 3 dup (0, 1, 2)

定义了9个字节,相当于db 0, 1, 2, 0, 1, 2, 0, 1, 2

实验七 寻址方式在结构化数据访问中的应用

kotlin 复制代码
assume cs:code, ds:data, ss:table
 
data segment
  db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
  db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
  dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
  dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
  dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
  dw 11542,14430,15257,17800
data ends
 
table segment
  db 21 dup ('year summ ne ?? ')
table ends

答案如下:

css 复制代码
assume cs:code, ds:data, ss:table
 
data segment
  db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
  db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
  dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
  dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
  dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
  dw 11542,14430,15257,17800
data ends
 
table segment
  db 21 dup ('year summ ne ?? ')
table ends

code segment
  start: mov ax, data
	   mov ds, ax
	   mov ax, table
	   mov ss, ax     ;保存table段地址

	   mov bx, 0       ;定位data段中的年份和总收入数据
	   mov  si, 0       ;定位data段中的雇员人数数据
         mov bp, 0        ;定位table段行地址
	   mov cx, 21

	s: mov ax, 00H[bx]
	   mov [bp], ax
	   mov ax, 00H[bx+2]
	   mov [bp+2], ax ;年份数据传送完毕

	   mov byte ptr [bp+4], ' '   ;添加空格
	     
	   mov ax, 54H[bx]
	   mov dx, 54H[bx+2]
	   mov [bp+5], ax
	   mov [bp+7], dx  ;总收入数据传送完毕

	   mov byte ptr [bp+9], ' ' ;添加空格

	   mov  di, 0A8H[si]
	   mov [bp+0AH], di ;雇员人数添加完毕 

	   mov byte ptr [bp+0CH], ' ';  添加空格
	   div di;
	   mov [bp+0DH], ax  ;  添加收入数据
	   mov byte ptr [bp+0FH],  ' '

	   add bx, 4
	   add si, 2
	   add bp, 10H
	   loop s

	mov ax, 4c00H
	int 21H
code ends

end start
相关推荐
小沈同学呀28 分钟前
创建一个Spring Boot Starter风格的Basic认证SDK
java·spring boot·后端
方圆想当图灵2 小时前
如何让百万 QPS 下的服务更高效?
分布式·后端
凤山老林3 小时前
SpringBoot 轻量级一站式日志可视化与JVM监控
jvm·spring boot·后端
凡梦千华3 小时前
Django时区感知
后端·python·django
Chan163 小时前
JVM从入门到实战:从字节码组成、类生命周期到双亲委派及打破双亲委派机制
java·jvm·spring boot·后端·intellij-idea
烈风5 小时前
004 Rust控制台打印输出
开发语言·后端·rust
用户21411832636025 小时前
用 AI 一键搞定!中医药科普短视频制作升级版
后端
秋难降5 小时前
零基础学习SQL(十一):SQL 索引结构|从 B+Tree 到 Hash,面试常问的 “为啥选 B+Tree” 有答案了
数据库·后端·mysql
SamDeepThinking6 小时前
用设计模式重构核心业务代码的一次实战
java·后端·设计模式