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
相关推荐
毅航1 小时前
从原理到实践,讲透 MyBatis 内部池化思想的核心逻辑
后端·面试·mybatis
展信佳_daydayup2 小时前
02 基础篇-OpenHarmony 的编译工具
后端·面试·编译器
Always_Passion2 小时前
二、开发一个简单的MCP Server
后端
用户721522078772 小时前
基于LD_PRELOAD的命令行参数安全混淆技术
后端
笃行3502 小时前
开源大模型实战:GPT-OSS本地部署与全面测评
后端
知其然亦知其所以然2 小时前
SpringAI:Mistral AI 聊天?一文带你跑通!
后端·spring·openai
庚云2 小时前
🔒 前后端 AES 加密解密实战(Vue3 + Node.js)
前端·后端
超级小忍2 小时前
使用 GraalVM Native Image 将 Spring Boot 应用编译为跨平台原生镜像:完整指南
java·spring boot·后端
倔强的石头3 小时前
Mihomo party如何在linux上使用
后端
灵魂猎手3 小时前
11. Mybatis SQL解析源码分析
java·后端·源码