《汇编语言》学习笔记-1
注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。
参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili
4 源程序到可执行程序过程
一个汇编语言源程序编写到执行的过程:
1)编写汇编源程序
2)先对源程序进行编译连接,编译产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行程序。
可执行文件包括两个部分:
- 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(如程序有多大、占用多少内存空间)
3)执行可程序文件。
CPU按照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如CS:IP执行第一条要执行的指令),然后由CPU执行程序。
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 文件名
然后输入文件名
masm 文件名;
4.3 连接
对源程序进行编译得到目标文件后,需要对目标文件进行连接,得到可执行文件。
连接使用的是LINK.exe程序,使用方式:
link 文件名
link 文件名;
连接的几个作用:
1)当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为多个目标文件后,再用连接程序将它们连接到一起,生成一个可执行执行。
2)程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标连接到一起,生成一个可执行文件。
3)一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,又不需要调用某个库中的子程序时,也必须用连接程序对目标文件进行处理,生成可执行程序。
4.4 执行
直接执行即可:
当前执行之后没有任何输出,所以没有什么提示。
DOS中的程序command.com是命令解释器,也就是DOS系统的shell。
如果用户要执行一个程序,输入可执行文件的名称,command会根据名称找到可执行文件,然后将这个可执行文件中的程序载入内存,设置CS:IP指向程序的入口。然后command暂停运行,CPU运行程序,程序返回后,返回到command中。
4.5 debug调试
可以使用debug.exe来跟踪程序的执行步骤:
1)debug.exe加载调试程序进入内存,进行相关的初始化后设置CS:IP指向程序的入口;CX寄存器为程序的长度。
2)DOS系统中EXE文件中的程序的加载过程:
程序加载后,DS寄存器中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区地址为DS:0。
内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信,从256字节处向后的空间存放的是程序。
3)使用t命令单步执行程序中的每一条指令,并观察每条指令的执行结果。
执行到了int 21,使用p命令执行。
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为止:
也可以使用g命令,执行到某个地址处:
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个字节:
运行指令方式,需要将IP指向0010偏移地址处。
这样比较麻烦,可以在程序中指明程序的入口位置:
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
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。
大写字母的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
; 将一个内存单元的内容送入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