2021-7-24~25 汇编语言 寄存器(内存访问)-办事大厅指南【完成版】(炉边小坐)

上文,我们主要从CPU 执行指令的角度聊寄存器,我们这次深入寄存器的内存,从内存的角度再理解寄存器。

我们介绍过,在(8086)CPU 内,我们用16bit 寄存器存储一个“字”,即字型数据,低 8 位存放低位数据,高 8 位存放高位数据,注意,低位字节排在高位字节前(从栈的角度而言,低位字节较为接近栈顶),总结一下,字单元的概念就是:存放一个 16bit 数据的内存单元(有两块地址连续的 8bit 地址单元组成,为什么这样设计上期有叙),而高低字节是可以转化的,并非定死的,这个后文再述,以后,我们将起始地址为 N 的字单元简称为 N 地址字单元,包含(N 与 N+1 的内存单元)。

CPU 要读取数据必先知其地址,而 8086 中的地址前文有述,是段地址加上偏移地址,8086 中有 ds 寄存器,用于存放即将要访问的数据的地址。例如,要读取 1000H 单元格的内容,我们可以这样进行

mov bx,1000H
mov ds,bx
mov al,[0]

解读:

mov al,[0]

前面我们使用 mov 指令,不但可以将数据直接送入寄存器,还可以实现数据在寄存器间的传送,当然也可以实现内存单元到寄存器的传送。当然,mov 指令有其使用语法:

mov target, source

target 指移动的目标,source 指被移动数据的源;当然这里只是 mov 指令的其中一种语法

还有几种语法:

mov

寄存器,数据

比如:mov ax,8

mov

寄存器,寄存器

比如:mov ax,bx

 

mov

寄存器,内存单元

比如:mov ax,[0]

mov

内存单元,寄存器

比如:mov [0],ax

mov

段寄存器,寄存器

比如:mov ds,ax

我们知道,只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址在哪里获取呢?在看到 mov al,[0] 这行指令的时候,你是不是也有这个疑问?事实上,指令执行时,8086CPU 自动取 ds 中的段地址为内存地址中的段地址。

那么 CPU如何用mov指令从10000H中读取数据呢?10000H用段地址和偏移地址表示的话就是1000:0,我们先将段地址1000H放入ds,然后再用mov al,[0]完成偏地址的传送。mov指令中的[ ]说明操作对象是一个内存单元,[ ]中的0说明这个内存单元的偏移地址是0,则它的段地址默认放在ds中,指令执行时,8086CPU会自动从ds中取出。

mov bx, 1000H
mov ds, bx

而在  mov al, [0]  完成数据从1000:0单元到al的传送前,应该完成 ds 中段地址的赋予,也就是 1000H ,所以有这两条指令

读到这里,你有没有开始思考,为什么要用两天指令完成对 ds 寄存器的赋值?你前面不是说了可以用 mov 指令直接完成对寄存器的赋值吗?这里为什么不可以 mov 直接对 ds 赋予一个内存单元的地址呢?

从理论上讲,我们可以用相似的方式:mov ds,1000H,来将1000H送入ds。可是,现实却无法这样做,因为8086CPU不支持将数据直接送入段寄存器的操作,注意,补充一个知识点,ds是一个段寄存器,所以 mov ds, 1000H这条指令是非法的。所以我们只好用一个寄存器(ax,bx,cx,dx 任一,四者无明显功能分别)来进行中转。

为什么8086CPU不支持将数据直接送入段寄存器的操作?这属于8086CPU硬件设计的问题,我们只要知道这一点就行了。(大佬可以去 intel 官网查找 8086 的细节,似乎下架了?)

问题3.2

写几条指令,将al中的数据送入内存单元10000H中,思考后看分析。

分析:

怎样将数据从寄存器送入内存单元?从内存单元到寄存器的格式是:“mov寄存器 名,内存单元地址”,从寄存器到内存单元则是:“mov内存单元地址,寄存器名”。 10000H可表示为1000:0,ds存放段地址1000H,偏移地址是0,mov [0],al可完成 从al10000H的数据传送。完整的几条指令是:

mov bx,1000H

mov ds,bx

mov [0],al

 我们之前说过,CPU 通过数据总线获取数据,有多少条数据线就可以传输多少位的数据,8086 是 16bit 的 CPU,因此拥有 16 条数据线,可以一次性传输一个字 ,只要在 mov 内给出 16bit 的地址,即可进行 16bit 数据的传输。

mov bx,1000H

mov ds,bx

mov ax,[0]//这里成功将 1000:0 的字型数据传入 ax

mov [0],cx// 这里将 16bit 寄存器 cx 中的数据传入 1000:0 处

问题3.3

内存中的情况如图3.2所示,写出下面的指令执行后寄存器ax,bx,cx中的值。

mov ax,1000H

mov ds, ax

        mov ax, [0]

mov bx, [2] 

       mov ex, [1]  

add bx, [1]

add ex, [2]

 

3.1指令执行与寄存器中的内容(1)

指令

执行后相关寄存器中的内容

说明

mov ax,1000H

ax=1000H

前两条指令的目的是将ds设为1000H

mov ds, ax

ds=1000H

mov ax, [0]

ax=1123H

1000:0处存放的字型数据送入ax

1000:1单元存放字型数据的高8位:11H, 1000:0单元存放字型数据的低8位:23H, 所以1000:0处存放的字型数据为1123Ho 指令执行时,字型数据的高8位送入ah,字 型数据的低8位送入al,ax中的数据为 1123H

mov bx, [2]

bx=6622H

这里说明了,低字节是由【】决定的!

mov cx, [1]

cx=2211H

add bx, [1]

bx=8833H

add ex, [2]

cx=8833H

看完了字的传送,我们来看看 add 和 sub 指令,以及前文已经提过的 mov 指令。

到现在,我们已知的 mov 通路为:1、数据——>寄存器;2、寄存器——>寄存器;3、内存单元——>寄存器;4、寄存器——>内存单元; 5、寄存器——>段寄存器;

我们学习 CPU 地址总线,数据总线等等的时候,知道地址总线或者数据总线都是双向联通的,那么 mov 的通路是否是互通的呢?(理论上,是的)【不验证,只给结论】

6、mov 寄存器,段寄存器;(即段寄存器——>寄存器)7、mov 内存单元,段寄存器;(段寄存器——>内存单元) 8、mov 段寄存器, 内存单元;(内存单元——>段寄存器)

而 add 与 sub 指令与 mov 指令一样,都要有两个操作对象,他们的语法形式如下:add 就是+,sub 就是-(减)的意思。

add寄存器,数据

比如:add ax,8

add寄存器,寄存器

比如:add ax,bx

add寄存器,内存单元

比如:add ax,[0]

add内存单元,寄存器

比如:add [0],ax

sub寄存器,数据

比如:sub ax,9

sub寄存器,寄存器

比如:subax,bx

sub寄存器,内存单元

比如:sub ax5[0]

sub内存单元,寄存器

比如:sub [0],ax

前面讲过,对于搭载8086CPU的计算机,在编程时,可以根据需要,将一组内存单元定义为一个段。

我们可以将一组长度为N(NW64KB)地址连续、起始地址为16的倍数 的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。比如用123B0H 123B9H这段内存空间来存放数据,我们就可以认为,123B0H123B9H这段内存是一个 数据段,它的段地址为123BH,长度为10个字节。                                ——《汇编语言》 王爽 著

 我们如何定义数据段?这个我们后文再叙,我们现在需要了解如何使用相关指令访问数据段中具体单元,例如将123B0H123B9H的内存单元定义为数据段。现在要累加这个数据段中的前 3个单元中的数据,代码如下:

mov ax,123BH

mov ds,ax ;将123BH送入ds中,作为数据段的段地址

mov al,0 ;用al存放累加结果

add alr [0] ;将数据段第一个单元(偏移地址为0)中的数值加到al中

add al,[1] ;将数据段第二个单元(偏移地址为1)中的数值加到al中

add al,[2] ;将数据段第三个单元(偏移地址为2)中的数值加到al中

注意,调用不同类型数据时,要注意数据类型大小与偏移量的关系,如写几条指令,累加数据段中的前3个字型数据:

mov ax,123BH

mov ds,ax ;将123BH送入ds中,作为数据段的段地址

mov ax,0 ;用ax存放累加结果

add ax,[0] ;将数据段第一个字(偏移地址为0)加到ax中

add ax,[2] ;将数据段第二个字(偏移地址为2)加到ax中

add ax,[4] ;将数据段第三个字(偏移地址为4)加到ax中

注意,一个字型数据占两个单元,所以偏移地址是024。

聊了那么多内存有关的东西,我们也许有个疑问,那个在 C 和 C++出镜率比较高的“栈”,去哪了?我们这就来介绍“栈” 。

我们在之前就知道了:栈是一种具有特殊的访问方式的存储空 间。它的特殊性就在于,最后进入这个空间的数据,最先岀去。并且明晰了栈顶的概念。栈作为一种受限线性表,仅在表尾进行插入和删除操作的线性表。进行数据操作的这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作入栈或压入(PUSH),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈(POP),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。具体关于栈的内容可参考我的文章:2021-7-15 从“栈”的角度看程序安排(炉边小坐)-Menou16

现如今的 CPU 都有栈的设计,8086 作为老前辈也是有的。8086CPU 有相关的指令,可以将一段内存作为“栈”来使用。这里明晰一个概念,出栈和入栈的数据操作都是以“字”byte 为单位的。

mov ax,0123H

push ax

mov bx,2266H

push bx                                                        上图描述了这样一段指令

mov cx,1122H

push cx

pop ax

pop bx

pop cx

但是我们还有疑问:其一、CPU 如何知道要将这一段内存当做栈使用?其二、如何确定栈顶?

我们之前讨论过,CPU如何知道当前要执行的指令所在的位置?我们现在知道,CSIP中存放着当前指令的段地址和偏移地址。现在的问题是:CPU如何知道栈顶的位置? 也有相应的寄存器来解决这个问题,这就是 SS:SP 道理与 CS:IP相同,CPU 从 SS:SP 得到栈顶地址,且任意时刻,SS:SP指向栈顶元素。push指令和pop指 令执行时,CPUSSSP中得到栈顶的地址。

注意注意注意:栈顶在地址较大一端!!!!(栈从高地址向低地址分配内存)

在了解了栈顶的规定后,我们可以理解 push 和 pop 指令的作用:

push ax的执行,由以下两步完成。

  1. SP=SP-2, SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶
  2. ax中的内容送入SS:SP指向的内存单元处SS:SP此时指向新栈顶。

有个很有趣的问题,我们关注一下

如果将10000H1000FH这段空间当作栈,初始状态栈是空的, SP=?

答:SP=0010H ,分析如下:

        将10000H1000FH这段空间当作栈段,SS=1000H,栈空间大小为16字节,栈最底部的字单元地址为1000:000E任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候SS=1000H, SP=000EH栈为就相当于栈中唯一的元素出栈,出栈后, SP=SP+2, SP原来000EH,加2后SP=10H,所以,当栈为空的时候,SS=1000H, SP=10H。

 换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地 址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H

pop ax的执行过程和push ax刚好相反,由以下两步完成。(弹出)

  1. SS:SP指向的内存单元处的数据送入ax中;
  2. SP=SP+2, SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

注意:出栈后,SS:SP指向新的栈顶1000EH, pop操作前的栈顶元素, 1000CH处的2266H依然存在,但是,它已不在栈中。当再次执行push等入栈指令后, SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值