1、汇编语言课件,王爽 著清华大学出版社,制作工具:Microsoft PowerPoint2003,本课件由汇编网()制作提供,第3 章寄存器(内存访问),3.1 内存中字的存储 3.2 DS和address 3.3 字的传送 3.4 mov、add、sub指令 3.5 数据段 3.6 栈 3.7 CPU提供的栈机制 3.8 栈顶超界的问题 3.9 push、pop指令 3.10 栈段,引言,在第2章中,我们主要从 CPU 如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令。 这一章中,我们从访问内存的角度继续学习几个寄存器。,在0地址处开始存放200
2、00:0号单元是低地址单元,1号单元是高地址单元。,3.1 内存中字的存储,3.1 内存中字的存储,问题: (1)0地址单元中存放的字节型数据是多少? (2)0地址字单元中存放的字型数据是多少? (3)2地址字单元中存放的字节型数据是多少?,3.1 内存中字的存储,问题(续): (4)2地址单元中存放的字型数据是多少? (5)1地址字单元中存放的字型数据是多少?结论,3.1 内存中字的存储,结论:任何两个地址连续的内存单元,N号单元和 N+1号单元,可以将它们看成两个内存单元 ,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元。,3.2 DS和address,CPU要读取一个内存单
3、元的时候,必须先给出这个内存单元的地址; 在8086PC中,内存地址由段地址和偏移地址组成。 8086CPU中有一个 DS寄存器,通常用来存放要访问的数据的段地址。 例如,3.2 DS和address,例如:我们要读取10000H单元的内容可以用如下程序段进行:mov bx,1000Hmov ds,bxmov al,0 上面三条指令将10000H(1000:0)中的数据读到al中。,3.2 DS和address,mov al,0 已知的mov指令可完成的两种传送功能: (1)将数据直接送入寄存器; (2)将一个寄存器中的内容送入另一个寄存器中。mov 指令 还可以将一个内存单元中的内容送入一个
4、寄存器。,3.2 DS和address,从哪个内存单元送到哪个寄存器中呢? mov指令的格式:mov 寄存器名,内存单元地址 “”表示一个内存单元, “”中的0表示内存单元的偏移地址。 那么内存单元的段地址是多少呢?,3.2 DS和address,执行指令时,8086CPU自动取DS中的数据为内存单元的段地址。 如何用mov指令从10000H中读取数据? 10000H表示为1000:0(段地址:偏移地址) 将段地址1000H放入ds 用mov al,0完成传送(mov指令中的说明操作对象是一个内存单元,中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中) 如何把1000H送入ds?
5、,3.2 DS和address,如何把1000H送入ds? 传送指令 mov ax,1 相似的方式 mov ds,1000H? 8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。(硬件设计的问题) mov ds,1000H 是非法的。 数据一般的寄存器段寄存器,3.2 DS和address,问题:写几条指令,将al中的数据送入内存单元10000H?(思考后分析) 分析问题本质:怎样将数据从寄存器送入内存单元? 结论:mov bx,1000Hmov ds,bxmov 0,al (一种合理的回答),3.3 字的传送,因为8086CPU是16位结构,有16根数据线,所以,可以一次
6、性传送16位的数据,也就是一次性传送一个字。,问题3.3:内存中的情况如右图,写出下面指令执行后寄存器ax,bx,cx中的值。思考后看分析。(单步跟踪),3.3 字的传送,问题3.3分析,问题3.4:内存中的情况如右图,写出下面指令执行后寄存器ax,bx,cx中的值。思考后看分析。(单步跟踪),3.3 字的传送,问题3.4分析,3.4 mov、add、sub指令,已学mov指令的几种形式:mov 寄存器,数据mov 寄存器,寄存器mov 寄存器,内存单元mov 内存单元,寄存器mov 段寄存器,寄存器 根据已知指令进行推测,3.4 mov、add、sub指令,根据已知指令进行推测: mov 段
7、寄存器,寄存器 mov 寄存器,段寄存器(验证) mov 内存单元,寄存器 mov 内存单元,段寄存器 mov 段寄存器,内存单元,验证(Debug),mov 段寄存器,寄存器 mov 寄存器,段寄存器,add和sub指令同mov一样,都有两个操作对象。它们可以对段寄存器进行操作吗?(请自行在Debug中试验),3.4 mov、add、sub指令,3.5 数据段,前面讲过,对于8086PC机,我们可以根据需要将一组内存单元定义为一个段。 我们可以将一组长度为N(N64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。 比如我们用123B0H123B
8、9H这段空间来存放数据: 段地址:123BH 长度:10字节,3.5 数据段,如何访问数据段中的数据呢?将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。 示例,3.5 数据段,我们将123B0H123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,代码如下:,3.5 数据段,问题3.5 写几条指令,累加数据段中的前3个字型数据。思考后看分析。,问题3.5分析,注意:一个字型数据占两个单元,所以偏移地址是0、2、4。,3.1节3.5节 小结,(1)字在内存中存储时
9、 ,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。 (2)用 mov 指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。 (3)address表示一个偏移地址为address的内存单元。,3.1节3.5节 小结(续),(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。 (5)mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。 (6)可以根据自己的推测,在Debug中实验指令的新格式。 特别提示,特别提示,检测点3.1 (p55)
10、没有通过检测点请不要向下学习!,3.6 栈,我们研究栈的角度:栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。 可以用一个盒子和3本书来描述栈的操作方式,3.6 栈,栈有两个基本的操作:入栈和出栈。 入栈:将一个新的元素放到栈顶; 出栈:从栈顶取出一个元素。 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。 栈的操作规则:LIFO(Last In First Out,后进先出),3.7 CPU提供的栈机制,现今的CPU中都有栈的设计。 8086CPU提供相关的指令来以栈的方式访问内存空间。 这意味着,我们在基于8086CPU编程的时候,可以将一
11、段内存当作栈来使用。,3.7 CPU提供的栈机制,8086CPU提供入栈和出栈指令: (最基本的)PUSH(入栈)POP (出栈)push ax:将寄存器ax中的数据送入栈中;pop ax :从栈顶取出数据送入ax。 8086CPU的入栈和出栈操作都是以字为单位进行的。,3.6 栈,下面举例说明,我们可以将10000H1000FH这段内存当作栈来使用。 下面一段指令的执行过程:mov ax,0123Hpush axmov bx,2266Hpush bxmov cx,1122Hpush cxpop axpop bxpop cx,3.6 栈,指令序列的执行过程演示 注意:字型数据用两个单元存放,高
12、地址单元放高 8 位,低地址单元放低8 位。 是否有疑惑?两个疑问,两个疑问,1、CPU如何知道一段内存空间被当作栈使用? 2、执行push和pop的时候,如何知道哪个单元是栈顶单元? 分析结论:任意时刻,SS:SP指向栈顶元素。,对于两个疑问的分析,回想:CPU如何指导当前要执行的指令所在的位置?寄存器CS和IP中存放着当前指令的段地址和偏移地址。8086CPU中,有两个寄存器:段寄存器SS 存放栈顶的段地址寄存器SP 存放栈顶的偏移地址任意时刻,SS:SP指向栈顶元素。,push 指令的执行过程,push ax (1)SP=SP2; (2)将ax中的内容送入SS:SP指向的内存单元处,SS
13、:SP此时指向新栈顶。图示,push 指令的执行过程,3.6 栈,问题3.6:如果我们将10000H1000FH 这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?思考后看分析。,问题3.6分析,SP = 0010H,问题3.6分析(续),我们将10000H1000FH 这段空间当作栈段,SS=1000H,栈空间大小为16 字节 ,栈最底部的字单元地址为1000:000E。任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS = 1000H,SP=000EH。,问题3.6分析(续),栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2 ,SP 原来为 000EH,
14、加 2 后SP=10H,所以,当栈为空的时候,SS=1000H,SP=10H。换个角度看,问题3.6分析(续),换个角度看:任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H。,pop 指令的执行过程,pop ax (1)将SS:SP指向的内存单元处的数据送入ax中; (2)SP = SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。图示,pop 指令的执行过程,注
15、意,pop 指令的执行过程,注意: 出栈后,SS:SP指向新的栈顶 1000EH,pop操作前的栈顶元素,1000CH 处的2266H 依然存在 ,但是,它已不在栈中。 当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。,3.8 栈顶超界的问题,SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?,3.8 栈顶超界的问题,当栈满的时候再使用push指令入栈,栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。栈顶超界是危险的。,3.8 栈顶超界的问题,栈顶超界是危险的:因
16、为我们既然将一段空间安排为栈 ,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。 (毕竟一个计算机系统并不是只有我们自己的程序在运行),3.8 栈顶超界的问题,但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。 我们当然希望CPU 可以帮我们解决这个问题,,3.8 栈顶超界的问题,比如说在CPU中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围 ,然后 ,CPU 在执行push指令的时候靠检测栈顶上限寄存器,在执行pop 指令的时候靠检测栈顶下限寄存器保证
17、不会超界。实际情况:8086CPU中并没有这样的寄存器。,3.8 栈顶超界的问题,8086CPU不保证对栈的操作不会超界。这就是说, 8086CPU 只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。这点就好像 ,CPU 只知道当前要执行的指令在何处(由CS:SP指示)而不知道读者要执行的指令有多少。从这两点我们可以看出,3.8 栈顶超界的问题,8086CPU的工作机理,只考虑当前的情况: 当前栈顶在何处; 当前要执行的指令是哪一条。结论,3.8 栈顶超界的问题,结论:我们在编程的时候要自己操心栈顶超界的问题 ,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多
18、而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。,3.9 push、pop指令,push和pop指令是可以在寄存器和内存之间传送数据的。push和pop指令的格式,栈与内存,栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。,3.9 push、pop指令,push和pop指令的格式(1) push 寄存器:将一个寄存器中的数据入栈 pop寄存器:出栈,用一个寄存器接收出栈的数据例如:push axpop bx,3.9 push、pop指令,push和pop指令的格式(2) push 段寄存器:将一个段寄存器中的数据入栈 pop段寄存
19、器:出栈,用一个段寄存器接收出栈的数据例如:push dspop es,3.9 push、pop指令,push和pop指令的格式(3) push内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位) pop 内存单元:出栈,用一个内存字单元接收出栈的数据 例如:push 0pop 2指令执行时 ,CPU 要知道内存单元的地址,可以在 push、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。,3.9 push、pop指令,问题3.7编程:将10000H1000FH 这段空间当作栈,初始状态是空的,将 AX、BX、DS中的数据入栈。思考后看分析。,问题3.7分析
20、,3.9 push、pop指令,问题3.8编程: (1)将10000H1000FH 这段空间当作栈,初始状态是空的; (2)设置AX=001AH,BX=001BH; (3)将AX、BX中的数据入栈; (4)然后将AX、BX清零; (5)从栈中恢复AX、BX原来的内容。 思考后看分析。,问题3.8分析,结论,问题3.8分析,从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时 ,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶 ,所以在恢复时,要最先出栈。,3.9 push、pop指令,问题3.9编程:(1)将10000H1000FH 这段空间当作栈,初始状态是空的;(2
21、)设置AX=002AH,BX=002BH;(3)利用栈 ,交换 AX 和 BX 中的数据。 思考后看分析。,问题3.9分析,3.9 push、pop指令,问题3.10 我们如果要在10000H处写入字型数据2266H,可以用以下的代码完成:mov ax,1000Hmov ds,axmov ,ax,2266Hmov 0,ax 补全下面的代码,3.9 push、pop指令,补全下面的代码,完成同样的功能:在10000H处写入字型数据2266H。_mov ax,2266Hpush ax 要求:不能使用“mov 内存单元,寄存器”这类指令 思考后看分析。,问题3.10分析,我们看需补全代码的最后两条指
22、令,将ax中的2266H压入栈中,也就是说,最终应由push ax将2266H写入10000H处。 问题的关键就在于:如何使push ax访问的内存单元是10000H。 Push指令是入栈指令。(注意执行过程) 完成程序,问题3.10分析(续),完成的程序:mov ax,1000Hmov ss,axmov sp,2mov ax,2266Hpush ax 结论,问题3.10分析(续),结论 push、pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。 同时,push和pop
23、指令还要改变 SP 中的内容。,问题3.10分析(续),我们要十分清楚的是,push和pop指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送,而执行push、pop指令却需要两步操作。 执行push时: 先改变SP,后向SS:SP处传送。 执行pop时: 先读取SS:SP处的数据,后改变SP。,注意,push、pop 等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0FFFFH。 提供:SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。 这就是8086CPU提供的栈操作机制。,栈的综述,(1)8086CPU提供了栈操作机制,方案如下:在S
24、S,SP中存放栈顶的段地址和偏移地址;提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元。 (2)push指令的执行步骤:1)SP=SP-2;2)向SS:SP指向的字单元中送入数据。 (3)pop指令的执行步骤: 1)从SS:SP指向的字单元中读取数据; 2)SP=SP-2。,栈的综述(续),(4)任意时刻,SS:SP指向栈顶元素。 (5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。 (6)用栈来暂存以后需要恢复的寄存器的内容时 ,寄存器出栈的顺序要和 入栈的顺序相反。 (7)push、pop实质上是一种内存传送指令,注意它们的灵活应用。栈是一种非常重要的机制
25、,一定要深入理解,灵活掌握。,3.10 栈段,前面讲过,对于8086PC机,在编程时,我们可以根据需要 ,将一组内存单元定义为一个段。 我们可以将长度为 N(N 64K )的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段。,3.10 栈段,比如我们将10010H1001FH 这段长度为 16 字节的内存空间当作栈来用,以栈的方式进行访问。 这段空间就可以成为栈段,段地址为1000H,大小为16字节。,3.10 栈段,将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就在执行push、pop 等栈操作指令时就自动地将我们定义的栈段当作栈空
26、间来访问。 如何使的如push、pop 等栈操作指令访问我们定义的栈段呢?将SS:SP指向我们定义的栈段。,3.10 栈段,问题3.11 如果我们将10000H1FFFFH这段空间当作栈段,初始状态是空的,此时,SS=1000H,SP=?思考后看分析。,问题3.11分析,我们将10000H1FFFFH这段空间当作栈段 ,SS=1000H ,栈空间大小为64KB ,栈最底部的字单元地址为1000:FFFE。 任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=FFFEH。,问题3.11分析,栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2。 SP原来为FFF
27、EH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。 换个角度看,问题3.11分析(续),换个角度看任意时刻,SS:SP指向栈顶元素,当栈为空的时候 ,栈中没有元素 ,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元 ,该单元的偏移地址为栈最底部的字单元的偏移地址+2 ,栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H。,问题3.12,一个栈段最大可以设为多少?为什么?思考后看分析。,问题3.12分析,一个栈段最大可以设为多少? 分析:这个问题显而易见,提出来只是为了提示我们将相关的知识融会起来。 首先从栈操作指令所完成的功能的角度上
28、来看,push、pop等指令在执行的时候只修改SP;,问题3.12分析,所以栈顶的变化范围是0FFFFH,从栈空时候的SP=0,一直压栈,直到栈满时SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。 所以一个栈段的容量最大为64KB。,段的综述,我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。 我们可以用一个段存放数据,将它定义为“数据段”; 我们可以用一个段存放代码,将它定义为“代码段”; 我们可以用一个段当作栈,将它定义为“栈段”;,段的综述(续),我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要: 对于数据段,
29、将它的段地址放在 DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段来访问;,段的综述(续),对于代码段,将它的段地址放在 CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;,段的综述(续),对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地置放在 SP 中,这样CPU在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们定义的栈段当作栈空间来用。,段的综述(续),可见,不管我们如何安排 ,CPU 将内存中的某段内存当作代码 ,是因为CS:IP指向了那里;CPU将某段内存当作栈 ,是因为
30、SS:IP 指向了那里。,段的综述(续),我们一定要清楚 ,什么是我们的安排,以及如何让CPU按我们的安排行事。 要非常的清楚CPU的工作机理,才能在控制CPU来按照我们的安排运行的时候做到游刃有余。,段的综述(续),比如我们将10000H1001FH安排为代码段,并在里面存储如下代码:,mov ax,1000Hmov ss,axmov sp,0020H ;初始化栈顶mov ax,csmov ds,ax ;设置数据段段地址mov ax,0add ax,2mov bx,4add bx,6push axpush bxpop axpop bx,段的综述(续),设置CS=1000H,IP=0,这段代码将得到执行。 可以看到,在这段代码中,我们又将10000H1001FH 安排为栈段和数据段。 10000H1001FH这段内存,既是代码段,又是栈段和数据段。,段的综述(续),一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。 关键在于CPU中寄存器的设置,即:CS、IP、SS、SP、DS的指向。,特别提示,检测点3.2 (page70)没有通过检测点请不要向下学习!,小结,