《汇编语言》学习笔记-1

注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。

参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili

4 源程序到可执行程序过程

一个汇编语言源程序编写到执行的过程:汇编编译执行

1)编写汇编源程序

2)先对源程序进行编译连接,编译产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行程序。

可执行文件包括两个部分:

  • 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
  • 相关的描述信息(如程序有多大、占用多少内存空间)

3)执行可程序文件。

CPU按照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如CS:IP执行第一条要执行的指令),然后由CPU执行程序。

页-10

4.1 源程序

一个简单的源程序:

assume cs:codesg	; cs和codesg关联

codesg segment		; 定义一个段codesg开始

    mov ax,0123H	; 由CPU执行的汇编指令
    mov bx,0456H
    add ax,bx
    add ax,ax

    mov ax,4c00H	; 程序返回
    int 21H

codesg ends			; 定义一个段codesg结束

end	; 汇编程序结束标记

(1)伪指令:汇编语言中,包括两种指令,一种是汇编指令,一种是伪指令。汇编指令有对应的机器码的指令,可以被编译为机器指令,最终由CPU执行。伪指令没有对应的机器指令,由编译器执行,编译器根据伪指令来进行相关的编译工作。

段名 segment
	...
段名 ends

segment和ends是一个成对使用的伪指令,segment和ends定义一个段,segment说明一个段开始;ends说明一个段结束。一个段必须有一个名称来标识。

段:一个汇编程序由多个段组成,这些段用来存放代码、数据或当作栈空间来使用。指令、数据、栈,被划分到不同的段中。一个汇编程序必须至少有一个段,用来存放代码。

(2)end

end是一个汇编程序的结束标记,编译器碰到伪指令end,就结束对源程序的编译。

(3)assume

assume将有特定用途的段和相关的段寄存器关联起来。

(4)程序返回

一个程序结束后,将CPU的控制权交还给使它得以运行的程序,这个过程称为程序返回。

mov ax,4c00H	; 程序返回
int 21H

4.2 编译

使用masm.exe程序进行编译,有以下几种方式:

masm 文件名
然后输入文件名

image-20220822222325746

masm 文件名;

image-20220822221932192

4.3 连接

对源程序进行编译得到目标文件后,需要对目标文件进行连接,得到可执行文件。

连接使用的是LINK.exe程序,使用方式:

link 文件名

image-20220822222605310

link 文件名;

image-20220822222646029

连接的几个作用:

1)当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为多个目标文件后,再用连接程序将它们连接到一起,生成一个可执行执行。

2)程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标连接到一起,生成一个可执行文件。

3)一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,又不需要调用某个库中的子程序时,也必须用连接程序对目标文件进行处理,生成可执行程序。

4.4 执行

直接执行即可:

image-20220822223319596

当前执行之后没有任何输出,所以没有什么提示。

DOS中的程序command.com是命令解释器,也就是DOS系统的shell。

如果用户要执行一个程序,输入可执行文件的名称,command会根据名称找到可执行文件,然后将这个可执行文件中的程序载入内存,设置CS:IP指向程序的入口。然后command暂停运行,CPU运行程序,程序返回后,返回到command中。

4.5 debug调试

可以使用debug.exe来跟踪程序的执行步骤:

1)debug.exe加载调试程序进入内存,进行相关的初始化后设置CS:IP指向程序的入口;CX寄存器为程序的长度。

image-20220822224832831

image-20220822225541057

2)DOS系统中EXE文件中的程序的加载过程:

image-20220822225216214

程序加载后,DS寄存器中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区地址为DS:0

内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信,从256字节处向后的空间存放的是程序。

3)使用t命令单步执行程序中的每一条指令,并观察每条指令的执行结果。

执行到了int 21,使用p命令执行。

image-20220822225852997

image-20220822225917253

5 [BX]和loop指令

# 将一个内存单元的内容送入ax,长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov ax, [bx]
# 将一个内存单元的内容送入al中,内存单元的长度为1字节,存放一个字节,偏移地址在bx中,段地址在ds中。
mov al, [bx]

5.1 [...]和(...)的规定

[...]:汇编语法规定,表示一个内存单元

指令 段地址 偏移地址 操作单位
mov ax, [0] 在DS中 在[0]中
mov al, [0] 在DS中 在[0]中 字节
mov ax, [bx] 在DS中 在[bx]中
mov al, [bx] 在DS中 在[bx]中 字节

(...):为学习方便而做出的约定,表示一个内存单元或寄存器中的内容。

“(...)”中的元素可以是三种类型:寄存器名、段寄存器名、内存单元的物理地址(20位)。

"(X)"所表示的数据有两种类型:字节、字。是哪种类型由寄存器名或具体的运算决定。

描述对象 描述方法 描述对象 描述方法
ax中的内容为0010H (ax)=0010H 2000:1000处的内容为0010H (21000H)=0010H
mov ax, [2]的功能 (ax)=((ds)*16+2) mov [2], ax的功能 ((ds)*16+2)=(ax)
add ax, 2的功能 (ax)=(ax)+2 add ax,bx的功能 (ax)=(ax)+(bx)
push ax的功能 (sp)=(sp)-2
((ss)*16+(sp))=(ax)
pop ax的功能 (ax)=((ss)*16+(sp))
(sp)=(sp)+2

5.2 符号idata表示常量

为了方便,约定idata表示常量。

mov ax, [idata]	; 代表mov ax, [1]、mov ax, [2] ...
mov bx, idata
mov ds, idata

5.3 loop指令

loop指令的格式:

loop 标号

CPU执行loop指令时,需要进行两步:①(cx)=(cx)-1;②判断cx中的值,不为0则跳转到标号处执行程序,如果为0则向下执行。

比如,设(ax)=2,N*2可用N+N实现,计算2^12如下:

assume cs:code

code segment
    mov ax, 2

    mov cx, 11
s:  add ax, ax	; 标号代表一个地址,这个地址处有一条指令:add ax, ax
    loop s		; (cx)=(cx)-1,判断cx中的值,不为0则跳转到s继续执行

    mov ax, 4c00H	; (cx)为0,则执行下一条语句
    int 21H

code ends

end

debug调试时,可以使用p命令一次将循环执行完毕,直到(cx)=0为止:

image-20220823213836157

也可以使用g命令,执行到某个地址处:

image-20220823213950033

5.4 debug和汇编编译器masm对指令的不同处理

目的:将内存2000:0、2000:1、2000:2、2000:3单元中的数据送入al、bl、cl、dl中。

(1)debug.exe中编程实现

mov ax, 2000
mov ds, ax
mov al, [0]
mov bl, [1]
mov cl, [2]
mov dl, [3]

(2)汇编程序实现

错误的方式:

assume cs:code
code segment

mov ax, 2000H
mov ds, ax
mov al, [0] ; 实际当作了mov al, 0处理
mov bl, [1] ; 实际当作了mov bl, 1处理
mov cl, [2] ; 实际当作了mov cl, 2处理
mov dl, [3] ; 实际当作了mov dl, 3处理

code ends
end

可看出,处理方式是不一样的,汇编程序可选择的方式1:

mov ax, 2000H
mov ds, ax	; 段地址2000H送入ds
mov bx, 0	; 偏移地址0送入bx
mov al, [bx]; ds:bx单元中的数据送入al

可选择的方式2:

assume cs:code
code segment

mov ax, 2000H 	
mov ds, ax		; 段地址2000H送入ds
mov al, ds:[0] 	; ds:0单元中的数据送入al
mov bl, ds:[1] 	; ds:1单元中的数据送入bl
mov cl, ds:[2] 	; ds:2单元中的数据送入cl
mov dl, ds:[3] 	; ds:3单元中的数据送入dl

code ends
end

总结:(1)在汇编程序中,如果用指令访问一个内存单元,则在指令中必须使用"[...]"来表示内存单元,但如果在“[]"里用一个常量idata直接给出内存单元的偏移地址,则需要在”[]“的前面显示地给出段地址所在的段寄存器,比如:

mov al, ds:[0]

(2)如果在"[]"里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。

5.5 loop和[bx]的组合使用

实验:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中。

实现方式:将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,达到两个运算对象的类型匹配并且结果不会超界。

assume cs:code

code segment

	mov ax, 0ffffH	; 汇编语言写法,必须以0开头
	mov ds, ax	; 设置(ds)=ffffH
	mov bx, 0	; 初始化ds:bx指向ffff:0
	
	mov dx, 0	; 初始化累加寄存器dx, (dx)=0
	
	mov cx, 12	; 初始化循环计数寄存器cx, (cx)=12
	
s: 	mov al, [bx]
	mov ah, 0
	add dx, ax	; 间接向dx中加上((ds)*16+(bx))单元的数值
	inc bx		; ds:bx指向下一个单元
	loop s
	
    mov ax, 4c00H	; (cx)为0,则执行下一条语句
    int 21H

code ends

end

5.6 段前缀

访问内存单元时,可以显示的给出内存单元的段地址所在的段寄存器。

; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在ds中
mov ax, ds:[bx]	
; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在cs中
mov ax, cs:[bx]	
; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在ss中
mov ax, ss:[bx]	
; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在es中
mov ax, es:[bx]	
; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为0,段地址在ss中
mov ax, ss:[0]
; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为0,段地址在cs中
mov ax, cs:[0]

这些用在访问内存单元的指令中,用于显示地指明内存单元的段地址的”ds:“,”cs:“,”ss:“,”es:“,在汇编语言中称为段前缀。

使用段前缀完成:将内存ffff:0ffff:b单元中的数据复制到0:2000:20b单元中。

assume cs:code

code segment

	mov ax, 0ffffH
	mov ds, ax	; 设置(ds)=ffffH

	mov ax, 0020H
	mov es, ax	; (es)=0020H
	
	mov bx, 0	; (bx)=0,ds:bx指向ffff:0,es:bx指向0020:0
	
	mov cx, 12	; (cx)=12,循环12次
	
s: 	mov dl, [bx]	; (dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
	mov es:[bx], dl	; ((es)*16+(bx))=(dl),将dl中的数据送入0020:bx
	inc bx			; (bx)=(bx)+1
	loop s
	
    mov ax, 4c00H	; (cx)为0,则执行下一条语句
    int 21H

code ends

end

实验

向内存0:2000:23F依次写入数据063(3FH)

assume cs:code

code segment
	
	mov ax, 0020H
	mov ds, ax  ; 段地址
    mov bx, 0   ; 初始值
	mov cx, 64	; 循环64次
    
s:	mov ds:[bx], bx
    inc bx
	loop s
	
    mov ax, 4c00H
    int 21H

code ends

end

6 包含多个段的程序

程序需要存放数据,需要取得内存空间。

获取空间的方法有两种,一是加载程序的时候为程序分配;二是程序执行过程中向系统申请。本文主要讨论第一种。

如果需要在程序被加载的时候取得所需的空间,则必须在源程序中做出说明,通过定义段来进行内存空间的获取。

内存地址空间的分配一般是系统来分配的,在源程序中定义需要处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中,当可执行文件中的程序被载入内存时,这些数据也同时被加载入内存,需要处理的数据自然就获得了存储空间。

6.1 在代码段中使用数据

实验:计算下面8个数据的和,结果存放在ax寄存器中。

0123H、0456H、0789H、0abcH、0defH、0fedH、0cbaH、0987H

希望通过循环的方式来进行累加,在累加前,要将这些数据存储在一组地址连续的内存单元中。

assume cs:code

code segment
	
	; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
	dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
    
    mov bx, 0
    mov ax, 0
    mov cx, 8
s:	add ax, cs:[bx] ; 数据在代码段中,偏移地址起始为0
    add bx, 2
	loop s
	
    mov ax, 4c00H
    int 21H

code ends

end

debug运行程序,可以看到数据存放在代码段的前16个字节:

image-20220824212518416

image-20220824212634221

运行指令方式,需要将IP指向0010偏移地址处

image-20220824212835273

这样比较麻烦,可以在程序中指明程序的入口位置:

assume cs:code

code segment
	
	; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
	dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H

start:
    mov bx, 0
    mov ax, 0
    mov cx, 8
s:	add ax, cs:[bx] ; 数据在代码段中,偏移地址起始为0
    add bx, 2
	loop s
	
    mov ax, 4c00H
    int 21H

code ends

end start ; 通知编译器程序的入口在start

通过end指明程序指令的入口,可以得到程序的框架为:

assume cs:code

code segment
	;
	; 数据
	;
start:
	;
	; 代码
	;

code ends

end start ; 通知编译器程序的入口在start

6.2 在代码段中使用栈

实验:利用栈,将程序中定义的数据逆序存放。

0123H、0456H、0789H、0abcH、0defH、0fedH、0cbaH、0987H

思路:程序运行时,定义的数据存放到CS:0~CS:F中,依次将这8个字节单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。

问题:首先需要一段可作为栈的空间,这段空间可以由系统分配,可以定义在程序中通过定义数据来取得一段空间,将这段空间当作栈空间来用。

assume cs:codesg
code segment
	
	; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
	; 这16个字型数据,在程序加载时将获取16个字的内存空间,存放16个数据,有多余的空间
	; 在后面的程序中将这段空间当作栈来使用
	dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
	dw 0, 0, 0, 0, 0, 0, 0, 0
	
start:
	mov ax, cs
	mov ss, ax
	mov sp 30H	; 设置栈顶ss:sp指向cs:30,将CS:10~CS:2F当作栈使用
	
	mov bx, 0
	mov cx, 8
s:	push cs:[bx]; 压栈,从CS:30开始
	add bx, 2
	loop s		; 将代码段0~15单元中的8个字型数据依次入栈
	
	mov bx, 0
	mov cx, 8
s0:	pop cs:[bx]	; 出栈,放到CS:0开始
	add bx, 2
	loop s0		; 依次出栈8个字型数据到代码段0~15单元中
	
	mov ax, 4c00H
	int 21H

codeseg ends
end start		; 指明程序的入口在start

image-20220901203929420

6.3 将数据、代码、栈放入不同的段

前面编程时,我们需要注意用到数据和栈,将数据和栈放到一个段里面。编程的时候,需要注意何处是数据,何处是栈,何处是代码。这样存在的问题:

(1)把它们放到一个段中使程序显得混乱;

(2)如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中。

需要考虑如何存放数据、代码和栈?

可以定义需要的数据,或通过定义数据来取得栈空间。

assume cs:code, ds:data, ss:stack
data segment
	dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
data ends

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

code segment
start:
	; 初始化栈段寄存器
	mov ax, stack	; 将名称为stack的段的段地址送入ax
	mov ss, ax
	mov sp, 20H	; 设置栈顶ss:sp指向stack:20
	
	; 初始化数据段寄存器
	mov ax, data
	mov ds, ax	; ds指向data段
	
	; 入栈
	mov bx, 0	; ds:bx指向data段中的第一个单元
	mov cs, 8
s:	push [bx]
	add bx, 2
	loop s		; 将data段中的0~15单元中的8字型数据依次入栈
	
	; 出栈
	mov bx, 0
	mov cx, 8
s0:	pop [bx]
	add bx, 2
	loop s0		; 依次出栈8个字型数据到data段的0~15单元中
	
	mov ax, 4c00H
	int 21h

code ends
end start

代码段、数据段、栈段完全是用户的安排。如何让CPU执行段呢?

(1)源程序中为3个段起名,比如程序中的data、code、stack;

(2)使用伪指令”assume cs:code, ds:data, ss:stack“将cs、ds、ss分别和code、data、stack段相连。

(3)使用段的方式,比如:

mov ax, stack	; 将名称为stack的段的段地址送入ax
mov ss, ax
mov sp, 20H		; 设置栈顶ss:sp指向stack:2

7 灵活的定位内存

前面访问内存的方式主要有:[0]、[bx]。本章讲解一些其它方式。

7.1 and和or指令

(1)and指令:逻辑与指令,按位进行与运算。

mov al, 01100011B
and al, 00111011B
; 执行后得到al=00100011B

通过and指令可将操作对象的相应位设为0,其它位不变。

(2)or指令:逻辑或指令,按位进行或运算。

mov al, 01100011B
or  al, 00111011B
; 执行后得到al=01111011B

通过or指令可将操作对象的相应位设位1,其它位不变。

7.2 以字符形式给出的数据

在汇编程序中,以“......”的方式指明数据是以字符的形式给出的,编译器将把它们转换为对应的ASCLL码。

assume cs:code, ds:data
data segment
	db 'unIX'	; 相当于'db 75H, 6EH, 49H, 58H'
	db 'foRK'	; 相当于'db 66H, 6FH, 52H, 48H'
data ends

code segment
	
start:
	mov al, 'a'	; 相当于'mov al, 61H'
	mov bl, 'b'	; 相当于'mov al, 62H'
	
	mov ax, 4c00H
	int 21h
code ends
end start

7.3 大小写转换问题

实验:将datasg中的第一个字符串转换为大写,第二个字符串转换为小写。

assume cs:codesg, ds:datasg

datasg segment
	db 'BaSic'
	db 'iNfOrMaTiOn'
datasg ends

codesg segment
	start:
codesg ends

end start

思路:大写字母的ASCLL码值比小写字母的ASCLL码值小20H。

image-20220901212028411

大写字母的ASCLL码加20H,就转换为了小写,如果已经是小写,就不需要转换;

小写字母的ASCLL码减20H,就转换为了大写,如果已经是大写,就不需要转换。

assume cs:codesg, ds:datasg

datasg segment
	db 'BaSic'
	db 'iNfOrMaTiOn'
datasg ends

codesg segment
start:
	mov ax, datasg
    mov bx, 0
    mov cx, 5
s:	mov al, [bx]
	; 如果(al)>61H,则为小写字母的ASCLL码,则: sub al, 20H
	mov [bx], al
	inc bx
	loop s

codesg ends

end start

问题:如何进行值的判断?

解决方式:大写字母的ASCLL码的第5位为0,小写字母的ASCLL码的第5位为1,转换为小写字母的方式为直接将第5位置为1,转换为大写字母的方式为直接将第5位置为0。

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	; 将ASCLL码的第5位置为0,变为大写字母
	mov [bx], al
	inc bx
	loop s
	
	mov bx, 5
	mov cx, 11
s0:	mov al, [bx]
	or al, 00100000B	; 将ASCLL码的第5位置为1,变为小写字母
	mov [bx], al
	inc bx
	loop s0
	
	mov ax, 4c00h
	int 21h
	
codesg ends
end start

7.4 [bx+idata]

除了可以用[bx]来指明一个内存单元,还可以用一种更灵活的方式来指明内存:

[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx的数值加上idata)。

mov ax, [bx+200]	; (ax)=((ds)*16+(bx)+200)
; 也可以写成
mov ax, [200+bx]
mov ax, 200[bx]
mov ax, [bx].200

7.5 用[bx+idata]的方式进行数组的处理

实验:将datasg中的第一个字符串转换为大写,第二个字符串转换为小写。

思路:将两个字符串看作两个数组,一个从0地址开始存放,另一个从5开始存放。可以使用[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符。

assume cs:codesg, ds:datasg

datasg segment
	db 'BaSic'		 ; 起始地址为0
	db 'iNfOrMaTiOn' ; 起始地址为5
datasg ends

codesg segment
start:
	mov ax, datasg
	mov ds, ax
	mov bx, 0
	mov cx, 5
s:	mov al, [bx]	; 或者写为'mov al, 0[bx]'
	and al, 11011111b
	mov [bx], al	; 或者写为'mov 0[bx], al'
	mov al, [5+bx]	; 或者写为'mov al, 5[bx]'
	or al, 00100000b
	mov [5+bx], al	; 或者写为'mov 5[bx], al'
	inc bx
	loop s
	
codesg ends
end start

7.6 变址寄存器SI和DI

  • si:source index,源变址寄存器;
  • di:destination index,目标变址寄存器。

si和di是8086CPU中和bx功能相近的寄存器。si和di不能分为两个8位寄存器使用。

mov bx, 0
mov ax, [bx]

; 等同于
mov si, 0
mov ax, [si]

mov di, 0
mov ax, [di+123]

实验:用si和di实现将字符串'welcome to masm!'复制到它后面的数据区中。

codesg segment

datasg segment
	db 'welcome to masm!'	; 起始地址;datasg
	db '................'	; 起始地址;datasg + 16
datasg ends

start:
	mov ax, datasg
	mov ds, ax
	mov si, 0
	mov di, 16
	
	mov cx, 8
s:	mov ax, [si]
	mov [di], ax
	add si, 2
	add di, 2
	loop s
	
	mov ax, 4c00h
	int 21h

codesg ends
end start

7.7 [bx+si]和[bx+di]

[bx+si]和[bx+di]的含义类似。

[bx+si]:表示一个内存单元,它的偏移地址是(bx)+(si)(即bx中的数值加上si中的数值),称为基址+变址寻址方式。

; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中
mov ax, [bx+si]	; (ax)=((ds)*16+(bx)+(si))

; 也可以写成
mov ax, [bx][si]

7.8 [bx+si+idata]和[bx+di+idata]

[bx+si+idata]和[bx+di+idata]类似,以[bx+si+idata]为例进行讲解。

[bx+si+idata]:表示一个内存单元,它的偏移地址为(bx)+(si)+idata

image-20220903115205843

; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值再加上idata,段地址在ds中。
mov ax, [bx+si+idata]	; (ax)=((ds)*16+(bx)+(si)+idata)

; 等同于下面的格式
mov ax, [bx+idata+si]
mov ax, [idata+bx+si]
mov ax, idata[bx][si]
mov ax, [bx].idata[si]
mov ax, [bx][si].idata

7.9 不同的寻址方式的灵活运用

几种定位内存地址的方法(寻址方式):

形式 名称 特点 意义 示例
[idata] 直接寻址 用一个常量表示地址 可用于直接定位一个内存单元 mov ax, [200]
[bx] 寄存器间接寻址 用一个变量来表示内存地址 可用于间接定位一个内存单元 mov bx, 0
mov ax, [bx]
[bx+idata] 寄存器相对寻址 用一个变量和常量表示地址 可在一个起始地址的基础上用变量间接定位一个内存单元 mov bx, 4
mov ax, [bx+200]
[bx+si] 基址变址寻址 用两个变量表示地址 mov ax, [bx+si]
[bx+si+idata] 相对基址变址寻址 用两个变量和一个常量表示地址 mov ax, [bx+si+200]

实验1:将datasg段中的每个单词的头一个字母改为大写字母。

assume cs:codesg, ds:datasg
datasg segment
	db '1.file          '
	db '2.edit          '
	db '3.switch        '
	db '4.view          '
	db '5.options       '
	db '6.help          '
datasg ends

codeseg segment
start:
	mov ax, datasg
	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 4c00h
	int 21h
codesg ends
end start

实验2:将datasg段中的每个单词都改为大写字母。

思路:

  • 四个字符,看成一个4行8列的二位数组;
  • 要修改二维数组的每一行的前3列;
  • 构造4×3次的二重循环。
assume cs:codesg, ds:datasg
datasg segment
	db 'ibm     '
	db 'dec     '
	db 'dos     '
	db 'vax     '
datasg ends

codeseg segment
start:
	mov ax, datasg
	mov ds, ax
	
	mov bx, 0	; 每一行的起始地址
	mov cx, 4
s0:	mov dx, cx	; 暂存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, 8
	mov cx, dx	; 恢复外层循环的计数
	loop s0
	
	mov 4c00h
	int 21h
codesg ends
end start

上面用寄存器dx暂存cx的值比较浪费寄存器,可以用固定的内存空间保存数据。

s0:	mov ds:[40h], cx	; 暂存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, 8
	mov cx, ds:[40h]	; 恢复外层循环的计数
	loop s0

用内存空间保存数据,可能存在内存被修改的风险,因此可以考虑用栈保存数据

一般来说,在需要暂存数据的时候,我们都应该使用栈。

assume cs:codesg, ds:datasg

stacksg segment
	dw 0, 0, 0, 0
stacksg ends

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

codeseg segment
start:
	mov ax, stacksg
	mov ss, ax
	mov sp, 8
	mov ax, datasg
	mov ds, ax
	
	mov bx, 0	; 每一行的起始地址
	mov cx, 4
s0:	push, cx	; 暂存cx的值,将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, 8
	pop cx		; 恢复外层循环的计数,恢复cx
	loop s0
	
	mov 4c00h
	int 21h
codesg ends
end start
posted @ 2022-09-03 21:51  zhengcixi  阅读(298)  评论(0编辑  收藏  举报
回到顶部