学习参考:汇编语言(第2版)王爽
- 内存中字的存储
- DS 和 [address]
- mov、add、sub 指令
- 小结
- CPU 提供的栈机制
- push、pop 指令
- 小结
内存中字的存储
字单元的概念:字单元,即存放一个字形数据(16 位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字形数据的高位字节,低地址内存单元中存放字形数据的低位字节
我们将起始地址为 N 的字单元简称为 N 地址字单元。比如一个字单元由 2、3 两个内存单元组成,则这个字单元的起始地址为 2,我们可以说这是 2 地址字单元。
举例来说,下面有一个内存中字的存储
1 | |-----| |
对 0 地址字单元来说,0 号单元是低地址单元,1 号单元是高地址单元,则字型数据 4E20H 的低位字节存放在 0 号单元中,高位字节存放在 1 号单元中。
- 0 地址单元中存放的字节型数据是 20H
- 0 地址字单元中存放的字型数据是 4E20H
- …
综上,任何两个地址连续的内存单元,N 号单元和 N+1 号单元,可以将它们看成两个内存单元,也可以看成一个地址为 N 的字单元中的高位字节单元和低位字节单元
DS 和 [address]
DS 寄存器,通常用来存放要访问数据的段地址
比如我们要读取 10000H 单元的内容,可以用如下的程序段进行
1 | mov bx,1000H |
上面三条指令将 10000H (1000:0) 中的数据读到 al 中
下面详细说明指令的含义
1 | mov al,[0] |
使用 mov 指令讲一个内存单元的内容送入一个寄存器中。
“[…]” 表示一个内存单元,“[…]” 中的 0 表示内存单元的偏移地址。但是只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是在指令执行时,8086CPU 自动取 ds 中的数据为内存单元的段地址。
1 | mov bx,1000H |
*如何用 mov 指令从 10000H 中读取数据?
10000H 用段地址和偏移地址表示为 1000:0,我们先将 1000H 放入 ds,然后用 mov al,[0]
完成传送。若要用该指令完成数据从 1000:0 单元到 al 的传送,这条指令执行时,ds 中的内容应为段地址 1000H,所以在这条指令之前应该将 1000H 送入 ds
如何把一个数据送入寄存器?
8086CPU 不支持直接将数据送入段寄存器的操作,ds 是一个段寄存器,所以 mov ds,1000H
这条指令是非法的。所以,只好使用一个寄存器进行中转,即先将 1000H 送入一个一般的寄存器,如 bx,再将 bx 中的内容送入 ds。
Q:写几条指令,将 al 中的数据送入内存单元 10000H 中
A:
1 | mov bx,1000H |
mov、add、sub 指令
1 | mov 寄存器,数据 比如:mov ax,8 |
小结
- 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中
- 用 mov 指令访问内存单元,可以在 mov 指令中只给出单元的偏移地址,此时,段地址默认在 DS 寄存器中
- [address] 表示一个偏移地址为 address 的内存单元
- 在内存和寄存器之间传送字形数据时,高地址单元和高 8 位寄存器、低地址单元和低 8 位寄存器相对应
- mov、add、sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令
- 可以根据自己的推测,在 Debug 中实验指令的新格式
CPU 提供的栈机制
在基于 8086CPU 编程的时候,可以将一段内存当作栈来使用。8086CPU 提供入栈和出栈指令,最基本的两个是 PUSH(入栈)和 POP (出栈)
1 | push ax ;表示将寄存器 ax 中的数据送入栈中 |
8086CPU 的入栈和出栈操作都是以字为单位进行的
Q:CPU 如何知道栈顶的位置?
A:8086CPU 中,有两个寄存器,段寄存器 SS 和寄存器 SP,栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中,任意时刻,SS:SP 指向栈顶元素。push 指令和 pop 指令执行时,CPU 从 SS 和 SP 中得到栈顶地址
1 | push ax |
该指令的执行,由一下两步完成
- SP=SP-2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶
- 将 ax 中的内容送入 SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶
8086CPU 对 push 指令的执行过程如下图
可见,入栈时,栈顶从高地址向低地址方向增长
1 | pop ax |
该指令的执行过程和 push ax
相反,由一下两步完成
- 将 SS:SP 指向的内存单元处的数据送入 ax 中
- SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶
8086CPU 对 pop 指令的执行过程如下图
注意,出栈后,SS:SP 指向新的栈顶 1000EH,pop 操作前的栈顶元素,1000CH 处的 2266H 依然存在,但已不在栈中,当再次执行 push 等入栈指令后,SS:SP 移至 1000CH,并在里面写入新的数据,将其覆盖
Q:如果将 10000H ~ 1000FH 这段空间当作栈,初始状态是空的,此时,SS=1000H,SP=?
A:SP=0010H
换一个角度看,任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以 SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为 1000:000E,所以栈空时,SP=0010H
push、pop 指令
1 | push 寄存器 ; 将一个寄存器中的数据入栈 |
举例如下
1 | mov ax,1000H |
指令执行时,CPU 要知道内存单元的地址,可以从 push、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU 从 ds 中获得
Q:编程
- 将 10000H ~ 1000FH 这段空间当作栈,初始状态栈是空的
- 设置 AX=001AH,BX=001BH
- 利用栈,交换 AX 和 BX 中的数据
A:
1 | mov ax,1000H |
小结
- 8086CPU 提供了栈操作机制,方案如下
- 在 SS、SP 中存放栈顶的段地址和偏移地址
- 提供入栈和出栈指令,它们根据 SS:SP 指示的地址,按照栈的方式访问内存单元
- push 指令的执行步骤
- SP=SP-2
- 向 SS:SP 指向的字单元中送入数据
- pop 指令的执行步骤
- 从 SS:SP 指向的字单元中送出数据
- SP=SP+2
- 任意时刻,SS:SP 指向栈顶元素
- 8086CPU 只记录栈顶,栈空间的大小我们要自己管理
- 用栈来暂存以后要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反
- push、pop 实质上是一种内存传送指令,注意它们的灵活应用
Q:一个栈段最大可以设为多少?为什么?
A:从栈操作指令所完成的功能上看,push、pop 等指令在执行的时候只修改 SP,所以栈顶的变化范围为 0 ~ FFFFH,从栈空时候的 SP=0,一直压栈,直到栈满时 SP=0;如果再次压栈,栈顶将环绕,覆盖原来栈中的内容。所以一个栈段的容量最大为 64KB。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是,关键在于CPU 中寄存器的设置,即 CS、IP、SS、SP、DS 的指向