内存中字的存储
一个字两个字节
所以内存中用两个地址连续的内存单元存储一个字
该字的低位字节存放在低地址内存单元,高位字节存放在高地址内存单元
20000的十六进制是4E20H 高位就是4E,存在地址高一点的地址内存单元中
字单元
由此就有了 字单元: 由两个地址连续的内存单元组成
将起始地址为N的字单元简称为N地址字单元
如:地址为2,3的两个内存单元组成的字单元,称之为 2地址字单元
总结:
任何两个地址连续的内存单元,N号单元和N+l号单元
可以将它们看成两个内存单元
也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元
DS和[address]
DS是一个寄存器,通常用于存放要访问数据的地址,称为段寄存器
1 mov bx,1000H 2 mov ds,bx 3 mov al,[0]
将10000H[1000:0]中的数据读到al寄存器中
mov al,[0]
mov 寄存器名,内存单元地址
[...]表示一个内存单元,其中0表示偏移地址
而段地址是8086CPU 自动取ds中的数据为内存单元的段地址
注意,不能直接:mov ds 1000H
因为8086不支持将数据直接送入段寄存器,只能先送到一个一般的寄存器(如bx),再将bx内容送入ds
问题:将al中的数据送入内存单元10000H
答案:
1 mov bx,1000H 2 mov ds,bx 10000H的地址送入ds中 3 4 mov [0],al [0]是偏移地址,段地址从ds自动获取
字的传送
8086是16位结构,有16根数据线
所以一次可以传输16位数据,即一次传一个字
例如
1 mov bx,1000H 2 mov ds,bx 3 mov ax,[0] 1000:0处的字形数据送入ax 4 mov [0],cs cs中的16位数据送到1000:0处
例题 执行后,ax,bx,cx的值是多少
ax = 1123H, bx = 6622H, cx = 8833H
段
数据段
将一组地址连续,起始地址是16的倍数 的内存单元 当作专门存储数据的内存空间,从而定义了数据段
例如:
将123B0H ~ 123B9H 这段内存单元作为专门存储数据的内存空间
这段内存就是一个段地址为123BH,长为10个字节的数据段
将123B0H ~ 123B9H定义为数据段,将三个单元的数据累加到al
1 mov ax,123B 2 mov ds,ax 3 mov al,0 4 mov al,[0] 5 mov al,[1] 6 mov al,[2]
栈
栈是一种具有特殊访问方式的存储空间。
特殊处在于:最后进入这个空间的数据,最先出去
入栈:将一个新的元素放到栈顶
出栈:从栈顶取出一个元素
栈顶的元素总是最后入栈
需要出栈时,又最先被从栈顶取出
操作规则:LIFO(先进后出)
可以将一段内存当作栈来使用
指令:
push 入栈
push ax:将寄存器ax中的数据送入栈中
pop 出栈
pop ax 从栈顶取出数据送入ax
8086中入栈和出栈都是以字为单位进行(8086是16位,酷睿是64位)
1 mov ax,0123H 2 push ax 3 mov bx,2266H 4 push bx 5 mov cx,1122H 6 push cx 7 pop ax 8 pop bx 9 pop cx
注意,字形单元用两个单元存放,高地址单元存放高8位,低地址单元存放低8位
SS:SP
任意时刻,SS:SP指向栈顶元素
段寄存器SS 存放栈顶的段地址
寄存器SP 存放栈顶的偏移地址
执行push,pop时,cpu从SS和SP中得到栈顶的地址
push ax执行过程:
1 SP = SP - 2
2 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶
将10000H ~ 10005H 这段空间作为栈,初始状态栈为空
则SS:SP指向 栈空间最高地址单元 的 下一个单元(100006H)
由于SS:SP在任意时刻指向栈顶,而栈为空时 栈中无元素 也就没有栈顶
所以SS:SP 只能指向 栈的 最底部单元 下面的单元 (栈的顺序可以理解为地址小的在上面)
该单元的偏移地址为 栈最底部的 字 单元 的 偏移地址 +2
栈最底部字单元的地址为1000:0004,所以栈空时,SP = 0006H
pop指令执行过程:
栈顶超界
执行push后栈顶超出栈空间
执行pop后栈顶超出栈空间
栈空间之外可能是其它重要用途的数据代码等
而超出栈空间后的 数据会覆盖 原数据
也就是栈溢出
例题
1 将10000H~1000FH 这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈。
1 mov ax,1000H 2 mov ss,ax 设置栈的段地址,ss=1000H,不能直接向段寄存器ss中送入数据,所以用ax中转。 3 mov sp,0010H 设置栈顶的偏移地址,因栈为空,所以sp=0010H。 4 push ax 5 push bx 6 push ds
2 在10000H处写入字型数据2266H
1 mov ax,1000H 2 mov ds,ax 3 mov ax,2266H 4 mov [0],ax 5 6 mov ax,1000H 7 mov ss,ax 8 mov sp,2 执行push时,cpu会SP=SP-2 9 10 mov ax,2266H 11 push ax
由于push,pop执行时只修改SP,所以栈顶变化范围为0~FFFFH
栈段
我们可以将长度为N(N≤64KB)的一组
地址连续、起始地址为16的倍数的内存单元,当作栈空间来用
从而定义了一个栈段
比如,我们将10010H~1001FH 这段长度为16字节的内存空间当作栈来用
这段空间就可以称为栈段,段地址为1001H,大小为16字节
使用SS:SP指向我们自定义的栈段
一个栈段最大为64kb
因为push,pop指令只修改sp,栈段变化范围为0~FFFFH,所以有65536个字节,即64kb
问题
如果将10000H~1FFFFH 这段空间当作栈段,初始状态栈是空的,此时,
SS=1000H,SP=?
答
栈最底部的字单元地址为1000:FFFE。
栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2。
加2后SP=10000,数字太大,1不能存入SP,所以SP=0。
段的总结
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时
CPU就将我们定义的数据段中的内容当作数据来访问
对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中
这样CPU就将执行我们定义的代码段中的指令;
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中
这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。
比如我们将1000OH~1001FH 安排为代码段,并在里面存储如下代码:
1 mov ax,1000H 2 mov ss,ax 3 mov sp,0020H ;初始化栈顶 4 mov ax,cs 5 mov ds,ax ;设置数据段段地址 6 mov ax,[0] 7 add ax,[2] 8 mov bx,[4] 9 add bx,[6] 10 push ax 11 push bx 12 pop ax 13 pop bx
设置 CS=1000H,IP-0,这段代码将得到执行。
可以看到,在这段代码中,我们又将10000H~1001FH安排为栈段和数据段。
10000H~1001FH 这段内存,既是代码段,又是栈段和数据段。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。
关键在于 CPU中寄存器的设置,即CS、IP,SS,SP,DS的指向。
参考: 王爽 - 汇编语言 和 小甲鱼零基础汇编