• [汇编语言]寄存器(内部访问)



    一、内存中字的访问

    CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,比如我们从0地址开始存放20000(4E20H),如图所示:
    在这里插入图片描述
    0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两字节单元构成)0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在1号单元中。

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

    之后我们将起始地址为N的字单元称为N地址字单元。比如一个字单元是由2、3两个内存单元构成,起始地址是2,我们说是2地址字单元。


    二、DS和[address]

    CPU中要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存单元的地址由段地址和偏移地址构成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取10000H单元中的内容,可以用如下程序段进行:

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

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

    下面我们说明指令mov al, [0]的含义

    前面我们使用mov指令,可以完成两种传送

    1. 将数据直接送入寄存器
    2. 将一个寄存器中的内容送入另外一个寄存器

    也可以使用mov指令将一个内存单元中的内容送入另一个寄存器中。从哪一个内存单元送到哪一个寄存器中?在指令中必须指明。寄存器用寄存器名来指明,内存单元则需要用内存单元的地址来指明。显然,此时mov指令的格式应该是:mov 寄存器名,内存单元地址

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

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

    8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以mov ds,1000H这种指令是非法的。如何将1000H送入ds?只好用一个寄存器进行中转,如bx,再将bx中的内容送入ds

    从内存单元到寄存器的格式是:mov 寄存器名,内存单元地址
    从寄存器到内存单元则是:mov 内存单元地址,寄存器名

    mov bx, 1000H
    mov ds, bx
    mov ax, [0]	;1000:0处的字型数据送入ax
    mov [0], cx	;cs中16位数据送到1000:0
    • 1
    • 2
    • 3
    • 4

    三、mov、add、sub指令

    (a) 既然有mov 段寄存器, 寄存器,从寄存器向段寄存器传送数据,那么也应该有mov 寄存器, 段寄存器,从段寄存器向寄存器传送数据,有了推测,我们在debug下进行验证:
    在这里插入图片描述
    发现是正确的,所以mov 寄存器, 段寄存器是正确的指令

    (b) 既然有mov 内存单元,寄存器,从寄存器向内存单元传送数据,那么也应该有mov 内存单元,段寄存器,从段寄存器向内存单元传送数据。我们可以将段寄存器cs中的内容送入内存10000H处。

    mov ax, 1000H
    mov ds, ax
    mov [0], cs
    
    • 1
    • 2
    • 3

    在Debug中进行试验:

    在这里插入图片描述
    mov [0], cs执行后,cs中的内容写入1000:0内存单元中,如图:
    在这里插入图片描述
    高位字节是07H,低位字节是3FH


    四、数据段

    对于8086PC机,可以将一组内存单元定义为一个段,我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元专门当做存储数据的内存空间,从而就定义了一个数据段

    如何访问数据段中的数据?将一段内存当做数据段,是我们的一种安排,在具体操作的时候,用ds存放数据段的段地址,在根据需要,用相关指令访问数据段中的具体单元

    如,将123B0H ~ 123B9H的内存单元定义为数据段,现在要累加这个数据段中的前3个单元中的数据,代码如下:

    mov ax, 123B
    mov ds, ax	;123BH送入ds中,作为数据段的段地址
    mov al, 0	;用al存放累加结果
    add al, [0]	;将数据段第一个单元(偏移地址为0)中的数值加到al中
    add al, [1]	;将数据段第二个单元(偏移地址为1)中的数值加到al中
    add al, [2]	;将数据段第三个单元(偏移地址为2)中的数值加到al中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    写几条指令,累加数据段中的前3个字型数据:

    mov ax, 123B
    mov ds, ax	;ds存放数据段的段地址
    mov ax, 0	;用ax存放累加结果
    add ax, [0]
    add ax, [2]
    add ax, [4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因为一个字型数据占两个单元,所以偏移地址是0、2、4


    五、栈

    栈是一个具有特殊访问方式的存储空间,特殊性在于,最后进入这个空间的数据,最先出去
    8086CPU提供了入栈和出栈指令,最基本的两个是PUSH和POP,比如push ax是将寄存器ax中的数据送入栈中,pop ax表示从栈顶取出数据送入ax,8086CPU的入栈和出栈操作都是以字为单位进行的。

    之前我们讨论过,CPU是如何知道当前要执行的指令所在的位置?是用CS:IP中存放着当前指令的段地址和偏移地址。现在如何知道栈顶的位置?显然也应该有相应的寄存器来存放栈顶的地址,在8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS:SP中得到栈顶的地址

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

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

    在这里插入图片描述
    可以看出,入栈时,栈顶从高地址向低地址方向增长。

    那如果将10000H ~ 1000FH这段空间当作栈,初始状态栈是空的,此时SS = 1000H,SP = 多少?
    SP = 0010H,如图:
    在这里插入图片描述


    六、栈顶越界的问题

    8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶,但是如果能够保证入栈和出栈时,栈顶不会超出栈空间?

    8086CPU不保证我们对栈的操作不会越界,也就是说,只知道栈顶在何处(由SS:SP指示),但是并不知道要执行的指令有多少,因此,我们要自己小心栈顶越界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致越界。


    七、push、pop指令

    前面我们使用的push ax,pop ax,显然push和pop指令是可以在寄存器和内存之间传送数据的。

    push和pop指令的格式可以是如下几种形式:

    push 寄存器
    pop 寄存器
    
    push 段寄存器
    pop 段寄存器
    
    push 内存单元
    pop内存单元
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    比如:

    mov ax, 1000H
    mov ds, ax
    push [0]
    pop [2]
    
    • 1
    • 2
    • 3
    • 4

    编程实现:

    1. 将10000H ~ 1000FH这段空间当作栈,初始状态栈为空
    2. 设置AX = 001AH, BX = 001BH
    3. 将AX、BX中的数据入栈
    4. 将AX、BX清零
    5. 从栈中恢复AX、BX原来的内容
    mov ax, 1000H
    mov ss, ax
    mov sp, 0010H	;初始sp在最高地址的下一个单元
    
    mov ax, 001AH
    mov bx, 001BH
    
    push ax
    push bx
    
    sub ax, ax		;将ax清零,也可以用mov ax,0
    sub bx, bx		;sub ax, ax机器码两个字节,mov ax,0机器码3个字节
    
    pop bx
    pop ax
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    编程实现:

    1. 将10000H ~ 1000FH这段空间当作栈,初始状态栈为空
    2. 设置AX = 001AH, BX = 001BH
    3. 交换AX、BX的内容
    mov ax, 1000H
    mov ss, ax
    mov sp, 0010H	;初始sp在最高地址的下一个单元
    
    mov ax, 001AH
    mov bx, 001BH
    
    push ax
    push bx
    
    pop ax
    pop bx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    push ax是入栈指令,将在栈顶之上压入新的数据,执行过程是,先将SP-2,使得SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向新的栈顶单元

    注意:push和pop等栈操作指令,修改的只是SP,也就是说栈顶变化范围最大为0 ~ FFFFH


    八、栈段

    对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N <= 64KB)的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来使用,从而定义了一个栈段。比如我们将10010H ~ 1001FH这段长度为16个字节的内存空间当作栈来用,以栈的方式来进行访问。这段空间就称为一个栈段,段地址为1001H,大小为16字节

    一个栈段最大可以设为多少?
    栈顶的变化范围是0 ~ FFFFH,从栈空的时候,SP = 0,一直压栈,压满后SP = 0,如果再次压栈,栈顶将环绕,覆盖原先栈中的内容,所以一个栈段容量最大为64KB

  • 相关阅读:
    JAVA面向对象三大特征
    线上价格监测,如何才算到手价
    记录nvm use node.js版本失败,出现报错: exit status 1: ��û���㹻��Ȩ��ִ�д˲�����
    Django中自定义过滤器,最详细步骤来了
    LeetCode Hot100之十:239.滑动窗口最大值
    VMware 虚拟机安装 CentOS 7
    【D3.js】1.18-给 D3 标签添加样式
    docker技术简介
    使用内网端口映射方案,轻松实现U8用友ERP的本地部署异地远程访问——“cpolar内网穿透”
    RDD上的持久化操作
  • 原文地址:https://blog.csdn.net/weixin_51304981/article/details/126556873