ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-09-03 22:01:35  阅读:197  来源: 互联网

标签:汇编语言 mov 笔记 al 学习 si ax bx ds


注:本文档为“《汇编语言(第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

标签:汇编语言,mov,笔记,al,学习,si,ax,bx,ds
来源: https://www.cnblogs.com/mrlayfolk/p/16653790.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有