汇编语言学习笔记(三):寄存器(内存访问)

学习参考:汇编语言(第2版)王爽

  • 内存中字的存储
  • DS 和 [address]
  • mov、add、sub 指令
  • 小结
  • CPU 提供的栈机制
  • push、pop 指令
  • 小结

内存中字的存储

字单元的概念:字单元,即存放一个字形数据(16 位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字形数据的高位字节,低地址内存单元中存放字形数据的低位字节

我们将起始地址为 N 的字单元简称为 N 地址字单元。比如一个字单元由 2、3 两个内存单元组成,则这个字单元的起始地址为 2,我们可以说这是 2 地址字单元。

举例来说,下面有一个内存中字的存储

1
2
3
4
5
6
7
8
9
10
11
  |-----|
0 | 20H |
|-----|
1 | 4EH |
|-----|
2 | 12H |
|-----|
3 | 00H |
|-----|
4 | |
|-----|

对 0 地址字单元来说,0 号单元是低地址单元,1 号单元是高地址单元,则字型数据 4E20H 的低位字节存放在 0 号单元中,高位字节存放在 1 号单元中。

  • 0 地址单元中存放的字节型数据是 20H
  • 0 地址字单元中存放的字型数据是 4E20H

综上,任何两个地址连续的内存单元,N 号单元和 N+1 号单元,可以将它们看成两个内存单元,也可以看成一个地址为 N 的字单元中的高位字节单元和低位字节单元

DS 和 [address]

DS 寄存器,通常用来存放要访问数据的段地址

比如我们要读取 10000H 单元的内容,可以用如下的程序段进行

1
2
3
mov bx,1000H
mov ds,bx
mov al,[0]

上面三条指令将 10000H (1000:0) 中的数据读到 al 中

下面详细说明指令的含义

1
mov al,[0]

使用 mov 指令讲一个内存单元的内容送入一个寄存器中。

“[…]” 表示一个内存单元,“[…]” 中的 0 表示内存单元的偏移地址。但是只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是在指令执行时,8086CPU 自动取 ds 中的数据为内存单元的段地址。

1
2
mov bx,1000H
mov ds,bx

*如何用 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
2
3
mov bx,1000H
mov ds,bx
mov [0],al

mov、add、sub 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mov 寄存器,数据          比如:mov ax,8
mov 寄存器,寄存器 比如:mov ax,bx
mov 寄存器,内存单元 比如:mov ax,[0]
mov 内存单元,寄存器 比如:mov [0],ax
mov 寄存器,内存单元 比如:mov ax,[0]
mov 段寄存器,寄存器 比如:mov ds,ax
mov 寄存器,段寄存器 比如:mov ax,ds
mov 段寄存器,内存单元 比如:mov ds,[0]


add 寄存器,数据 比如:add ax,8
add 寄存器,寄存器 比如:add ax,bx
add 寄存器,内存单元 比如:add ax,[0]
add 内存单元,寄存器 比如:add [0],ax


sub 寄存器,数据 比如:sub ax,9
sub 寄存器,寄存器 比如:sub ax,bx
sub 寄存器,内存单元 比如:sub ax,[0]
sub 内存单元,寄存器 比如:sub [0],ax

小结

  1. 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中
  2. 用 mov 指令访问内存单元,可以在 mov 指令中只给出单元的偏移地址,此时,段地址默认在 DS 寄存器中
  3. [address] 表示一个偏移地址为 address 的内存单元
  4. 在内存和寄存器之间传送字形数据时,高地址单元和高 8 位寄存器、低地址单元和低 8 位寄存器相对应
  5. mov、add、sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令
  6. 可以根据自己的推测,在 Debug 中实验指令的新格式

CPU 提供的栈机制

在基于 8086CPU 编程的时候,可以将一段内存当作栈来使用。8086CPU 提供入栈和出栈指令,最基本的两个是 PUSH(入栈)和 POP (出栈)

1
2
push ax     ;表示将寄存器 ax 中的数据送入栈中
pop 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
2
3
4
5
6
7
8
push 寄存器     ; 将一个寄存器中的数据入栈
pop 寄存器 ; 出栈,用一个寄存器接收出栈的数据

push 段寄存器 ; 将一个段寄存器中的数据入栈
pop 段寄存器 ; 出栈,用一个段寄存器接收出栈的数据

push 内存单元 ; 将一个内存单元处的字入栈(注意:栈操作都是以字为单位)
pop 内存单元 ; 出栈,用一个内存单元接收出栈的字

举例如下

1
2
3
4
mov ax,1000H
mov ds,ax ;内存单元的段地址要放在 ds 中
push [0] ;将 1000:0 处的字压入栈中
pop [2] ;出栈,出栈的数据送入 1000:2 处

指令执行时,CPU 要知道内存单元的地址,可以从 push、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU 从 ds 中获得

Q:编程

  • 将 10000H ~ 1000FH 这段空间当作栈,初始状态栈是空的
  • 设置 AX=001AH,BX=001BH
  • 利用栈,交换 AX 和 BX 中的数据

A:

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,1000H
mov ss,ax ;设置栈的段地址,不能直接想段寄存器送入数据,需要使用寄存器做中转
mov sp,0010H ;设置偏移地址,栈为空,所以偏移地址应该为最低部单元的偏移地址+1

mov ax,001AH
mov bx,001BH

push ax
push bx

pop ax
pop bx

小结

  • 8086CPU 提供了栈操作机制,方案如下
    1. 在 SS、SP 中存放栈顶的段地址和偏移地址
    2. 提供入栈和出栈指令,它们根据 SS:SP 指示的地址,按照栈的方式访问内存单元
  • push 指令的执行步骤
    1. SP=SP-2
    2. 向 SS:SP 指向的字单元中送入数据
  • pop 指令的执行步骤
    1. 从 SS:SP 指向的字单元中送出数据
    2. 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 的指向

Author: Inno Fang
Link: http://innofang.github.io/2017/11/19/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E5%AF%84%E5%AD%98%E5%99%A8%EF%BC%88%E5%86%85%E5%AD%98%E8%AE%BF%E9%97%AE%EF%BC%89/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-ND 4.0 unless stating additionally.