📌 汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。 本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
[bx]是什么呢?
和[0]有些类似,[0]表示内存单元,它的偏移地址是0。比如在下面的指令中(但是注意这个是在 Debug 中使用的方式):
mov ax, [0]
将一个内存单元的内容送入 ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ds中。
mov al, [0]
将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。
要完整地描述一个内存单元,需要两种信息:①内存单元的地址;②内存单元的长度(类型)。
用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令:
mov ax, [bx]
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov al, [bx]
将一个内存单元的内容送入 al,这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在bx中,段地址在ds中。
英文单词“loop”有循环的含义,显然这个指令和循环有关。
我们在这一章的学习中,会讲解[bx]和 loop 指令的应用、意义和相关的内容。
为了描述上的简洁,在以后的内容中,我们将使用一个描述性的符号“()”来表示一个寄存器或一个内存单元中的内容。
比如:
❗注意,“()”中的元素可以有3种类型: ①寄存器名;②段寄存器名;③内存单元的物理地址(一个 20位数据)。
比如:
(ax)、(ds)、(al)、(cx)、(20000H)、((ds)*16+(bx))等是正确的用法;
(2000:0)、((ds):1000H)等是不正确的用法。
“(X)”所表示的数据有两种类型:①字节;②字。
是哪种类型由寄存器名或具体的运算决定,比如:
我们在 Debug 中写过类似的指令:mov ax,[0],表示将 ds:0处的数据送入 ax中。
指令中,在“[…]”里用一个常量 0表示内存单元的偏移地址。以后,我们用 idata 表示常量。比如:
mov ax,[idata]:代表mov ax,[l]、mov ax,[2]、mov ax,[3]等
mov bx,idata:代表mov bx,1、mov bx,2、mov bx,3等
mov ds,idata:代表mov ds,1、mov ds,2等,它们都是非法指令。
看一看下面指令的功能。
mov ax, [bx]
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入 ax中。即:(ax)=((ds)*16+(bx))。
mov [bx], ax
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA 处。即:((ds)*16+(bx))=(ax)。
程序和内存中的情况如下图所示,写出程序执行后,21000H-21007H单元中的内容。
思考后看分析。
注意,imc bx的含义是bx中的内容加1,比如下面两条指令:
mov bx, 1
inc bx
执行后,bx=2。
(1)先看一下程序的前3条指令:
mov ax, 2000H
mov ds, ax
mov bx, 1000H
这3条指令执行后,ds=2000H,bx=1000H。
(2)接下来,第4条指令:
mov ax, [bx]
指令执行前:ds=2000H,bx=1000H,则mov ax, [bx]将把内存2000:1000处的字型数据送入ax中。该指令执行后,ax=00beH。
(3)接下来,第5、6条指令:
inc bx
inc bx
这两条指令执行前 bx=1000H,执行后bx=1002H。
(4)接下来,第7条指令:
mov [bx], ax
指令执行前:ds=2000H,bx=1002H,则mov [bx], ax将把ax中的数据送入内存2000:1002处。指令执行后,2000:1002单元的内容为BE,2000:1003单元的内容为00。
(5)接下来,第8、9条指令:
inc bx
inc bx
这两条指令执行前 bx=1002H,执行后bx=1004H。
(6)接下来,第10条指令:
mov [bx], ax
指令执行前:ds=2000H,bx=1004H,则mov [bx], ax将把ax中的数据送入内存2000:1004处。指令执行后,2000:1004单元的内容为BE,2000:1005单元的内容为00。
(7)接下来,第11条指令:
inc bx
这条指令执行前 bx=1004H,执行后bx=1005H。
(8)接下来,第12条指令:
mov [bx], al
指令执行前:ds=2000H,bx=1005H,则mov [bx], al将把al中的数据送入内存2000:1005处。指令执行后,2000:1005单元的内容为 BE。
(9)接下来,第13条指令:
inc bx
这条指令执行前 bx=1005H,执行后bx=1006H。
(10)接下来,第14条指令:
mov [bx], al
指令执行前:ds=2000H,bx=1006H,则mov [bx],al将把al中的数据送入内存 2000:1006处。指令执行后,2000:1006单元的内容为 BE。
所有程序执行后,内存中的情况如下图所示。
loop指令的格式是:loop 标号。
CPU执行loop指令的时候,要进行两步操作:
①(cx)=(cx)-1;
②判断cx中的值,不为零则转至标号处执行程序,如果为零则继续向下执行。
从上面的描述中,可以看到,cx中的值影响着loop指令的执行结果。通常(注意,我们说的是通常)我们用loop指令来实现循环功能,cx中存放循环次数。
这里讲解loop指令的功能,关于loop指令如何实现转至标号处的细节,将在后面的内容中讲解。下面我们通过一个程序来看一下loop 指令的具体应用。
分析:设(ax)=2,可计算(ax)=(ax)*2,最后(ax)中为 2^2 的值。N*2 可用 N+N 实现,程序如下。
assume cs:code
code segment
mov ax,2
add ax, ax
mov ax,4c00h
int 2lh
code ends
end
分析:2^3=2*2*2,若设(ax)=2,可计算(ax)=(ax)*2*2,最后(ax)中为 2^3 的值。N*2可用 N+N 实现,程序如下。
assume cs:code
code segment
mov ax,2
add ax, ax
add ax, ax
mov ax,4c00h
int 2lh
code ends
end
分析:2^12=2*2*2*2*2*2*2*2*2*2*2*2,若设(ax)=2,可计算(ax)=(ax)*2*2*2*2*2*2*2*2*2*2*2,最后(ax)中为 2^12 的值。N*2 可用 N+N 实现,程序如下。
assume cs:code
code segment
mov ax,2
;做11次add ax, ax
mov ax,4c00h
int 2lh
code ends
end
可见,按照我们的算法,计算 2^12需要11条重复的指令 add ax,ax。我们显然不希望这样来写程序,这里,可用loop来简化我们的程序。
assume cs:code
code segment
mov ax,2
mov cx, 11
s: add ax, ax
loop s
mov ax,4c00h
int 2lh
code ends
end
下面分析一下这个程序。
在汇编语言中,标号代表一个地址,上面的程序中有一个标号s。它实际上标识了一个地址,这个地址处有一条指令:add ax, ax。
CPU执行loop s
的时候,要进行两步操作:
以下3条指令
mov cx, 11
s: add ax, ax
loop s
执行loop s
时,首先要将(cx)减1,然后若(cx)不为0,则向前转至s处执行 add ax, ax。所以,可以利用 cx 来控制 add ax, ax 的执行次数。
下面我们详细分析一下这段程序的执行过程,从中体会如何用cx和loop s
相配合实现循环功能。
(1)执行 mov cx, 11,设置(cx)=11;
(2)执行 add ax, ax(第1次);
(3)执行 loop s
将(cx)减1,(cx)=10,(cx)不为0,所以转至s处;
(4)执行 add ax, ax(第2 次);
(5)执行 loop s
将(cx)减1,(cx)=9,(cx)不为0,所以转至s处;
(6)执行 add ax, ax(第3 次);
(7)执行 loop s
将(cx)减1,(cx)=8,(cx)不为0,所以转至s处;
(8)执行 add ax, ax(第4次);
(9)执行 loop s
将(cx)减1,(cx)=7,(cx)不为0,所以转至s处;
(10)执行 add ax, ax(第5次);
(11)执行loop s
将(cx)减1,(cx)=6,(cx)不为0,所以转至s处;
(12)执行 add ax, ax(第6次);
(13)执行 loop s
将(cx)减1,(cx)=5,(cx)不为0,所以转至s处;
(14)执行 add ax, ax(第7次);
(15)执行 loop s
将(cx)减1,(cx)=4,(cx)不为0,所以转至s处;
(16)执行 add ax, ax(第8次);
(17)执行loop s
将(cx)减1,(cx)=3,(cx)不为0,所以转至s处;
(18)执行 add ax, ax(第9次);
(19)执行 loop s
将(cx)减1,(cx)=2,(cx)不为0,所以转至s处;
(20)执行 add ax, ax(第 10 次);
(21)执行 loop s
将(cx)减1,(cx)-1,(cx)不为0,所以转至s处;
(22)执行 add ax, ax(第11次);
(23)执行loop s
将(cx)减1,(cx)=0,(cx)为0,所以向下执行。(结束循环)
从上面的过程中,我们可以总结出用cx和loop指令相配合实现循环功能的3个要点: (1)在cx中存放循环次数; (2)loop指令中的标号所标识地址要在前面; (3)要循环执行的程序段,要写在标号和loop 指令的中间。
用cx和loop指令相配合实现循环功能的程序框架如下。
mov cx, 循环次数
s:
循环执行的程序段
loop s
编程,用加法计算123*236,结果存在ax中。思考后看分析。
分析: 可用循环完成,将123加236 次。可先设(ax)=0,然后循环做236次(ax)=(ax)+123 程序如下。 程序:
assume cs:code
code segment
mov ax, 0
mov cx, 236
s: add ax, 123
loop s
mov ax,4c00h
int 2lh
code ends
end
改进问题一答案的程序,提高123*236的计算速度。思考后看分析。
分析:
之前的程序做了236次加法,我们可以将236加123次。可先设(ax)=0,然后循环做123 次(ax)=(ax)+236,这样可以用123次加法实现相同的功能。
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!