汇编基础教程段的定义应用详解
段
将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元
种类
代码段
定义
对于8086pc机,在编程时,可以根据需要,将一组内存单元定义为一个段。
可以将长度为 n( n≤64kb )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。
例如
这段长度为 10 字节的字节的指令,存在从123b0h~123b9h的一组内存单元中,我们就可以认为,123b0h~123b9h这段内存单元是用来存放代码的 ,是一个代码段 ,它的段地址为123bh,长度为10字节。
如何使得代码段中的指令被执行
- cpu 只认被 cs:ip 指向的内存单元中的内容为指令。
- 所以要将cs:ip指向所定义的代码段中的第一条指令的首地址。
- cs = 123bh,ip = 0000h。
数据段
定义
我们可以将一组长度为n(n≤64k)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
比如我们用123b0h~123b9h这段空间来存放数据:
- 段地址:123bh
- 长度:10字节
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
ds和[address]
我们要读取10000h单元的内容
cpu要读取一个内存单元的时候,必须先给出这个内存单元的地址
- 在8086pc中,内存地址由段地址和偏移地址组成。
- 8086cpu中有一个 ds寄存器,通常用来存放要访问的数据的段地址。
将10000h(1000:0)中的数据读到al中。
mov bx,1000h
mov ds,bx
mov al,[0]
mov 指令可以将一个内存单元中的内容送入一个寄存器。
mov指令的格式:
- mov 寄存器名,内存单元地址
- “[…]”表示一个内存单元, “[…]”中的0表示内存单元的偏移地址。
如何用mov指令从10000h中读取数据?
- 10000h表示为1000:0(段地址:偏移地址)
- 将段地址1000h放入ds
- 用mov al,[0]完成传送(mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中)
如何把1000h送入ds?
- 8086cpu不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。(硬件设计的问题)
- mov ds,1000h 是非法的。
- 数据>>一般的寄存器>>段寄存器
例如
我们将123b0h~123bah的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,
代码如下:
栈段
栈
栈空间当然也是内存空间一部分,它只是一段可以以一种特殊方式进行访问的内存空间。
操作方式
栈有两个基本的操作
入栈:将一个新的元素放到栈顶
出栈:从栈顶取出一个元素
栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出
栈的操作规则:lifo(last in first out,后进先出)
8086cpu的入栈和出栈操作都是以字为单位进行的。
注意:字型数据用两个单元存放,高地址单元放高 8 位,低地址单元放低8 位。
8086cpu提供入栈和出栈指令
ss:sp
8086cpu中,有两个寄存器:
- 段寄存器ss 存放栈顶的段地址
- 寄存器sp 存放栈顶的偏移地址
任意时刻,ss:sp指向栈顶元素。
ss和sp只记录了栈顶的地址,依靠ss和sp可以保证在入栈和出栈时找到栈顶。
栈顶超界的问题
种类
- 当栈满的时候再使用push指令入栈,
- 栈空的时候再使用pop指令出栈,
8086cpu不保证对栈的操作不会超界。
8086cpu 只知道栈顶在何处(由ss:sp指示),而不知道读者安排的栈空间有多大。
8086cpu的工作机理,只考虑当前的情况:
- 当前栈顶在何处;
- 当前要执行的指令是哪一条。
解决办法
- 要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界
- 执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。
push、pop指令
定义
push和pop指令是可以在寄存器和内存之间传送数据的。
push(入栈)
push ax:将寄存器ax中的数据送入栈中;
执行过程 push ax
(1)sp=sp–2;
(2)将ax中的内容送入ss:sp指向的内存单元处,ss:sp此时指向新栈顶。
图示
入栈时,栈顶从高地址向低地址方向增长
pop(出栈)
pop ax :从栈顶取出数据送入ax。执行过程 (1)将ss:sp指向的内存单元处的数据送入ax中;(2)sp = sp+2,ss:sp指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
图示
注意:
出栈后,ss:sp指向新的栈顶 1000eh,pop操作前的栈顶元素,1000ch 处的2266h 依然存在 ,但是,它已不在栈中。
当再次执行push等入栈指令后,ss:sp移至1000ch,并在里面写入新的数据,它将被覆盖。
格式
(1)
push 寄存器:将一个寄存器中的数据入栈
pop 寄存器:出栈,用一个寄存器接收出栈的数据
例如:push axpop bx
(2)
push 段寄存器:将一个段寄存器中的数据入栈 pop
段寄存器:出栈,用一个段寄存器接收出栈的数据
例如:push dspop es
(3)
push 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
pop 内存单元:出栈,用一个内存字单元接收出栈的数据
例如:push [0] pop [2]
指令执行时 ,cpu 要知道内存单元的地址,可以在 push、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,cpu从ds中取得。
注意
与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由ss:sp指出的。
cpu执行mov指令只需一步操作,就是传送,而执行push、pop指令却需要两步操作
执行push时:先改变sp,后向ss:sp处传送。
执行pop时: 先读取ss:sp处的数据,后改变sp。
push、pop 等栈操作指令,修改的只是sp。也就是说,栈顶的变化范围最大为:0~ffffh。
提供:ss、sp指示栈顶;改变sp后写内存的入栈指令;读内存后改变sp的出栈指令。
栈段定义
- 可以根据需要 ,将一组内存单元定义为一个段
- 比如我们将10010h~1001fh 这段长度为 16 字节的内存空间当作栈来用,以栈的方式进行访问。
- 我们可以将长度为 n(n ≤64k )的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段
- 这段空间就可以成为栈段,段地址为1000h,大小为16字节
- 如何使的如push、pop 等栈操作指令访问我们定义的栈段
- 将ss:sp指向我们定义的栈段。
思考
我们将10000h~1ffffh这段空间当作栈段 ,ss=1000h ,栈空间大小为64kb ,栈最底部的字单元地址为1000:fffe。
任意时刻,ss:sp指向栈顶,当栈中只有一个元素的时候,ss=1000h,sp=fffeh。
栈为空,就相当于栈中唯一的元素出栈,出栈后,sp=sp+2。
sp原来为fffeh,加2后sp=0,所以,当栈为空的时候,ss=1000h,sp=0。
任意时刻,ss:sp指向栈顶元素,当栈为空的时候 ,栈中没有元素 ,也就不存在栈顶元素,所以ss:sp只能指向栈的最底部单元下面的单元 ,该单元的偏移地址为栈最底部的字单元的偏移地址+2 ,栈最底部字单元的地址为1000:fffe,所以栈空时,sp=0000h。
访问
对于数据段,将它的段地址放在 ds中,用mov、add、sub等访问内存单元的指令时,cpu就将我们定义的数据段中的内容当作数据段来访问;
对于代码段,将它的段地址放在 cs中,将段中第一条指令的偏移地址放在ip中,这样cpu就将执行我们定义的代码段中的指令;
对于栈段,将它的段地址放在ss中,将栈顶单元的偏移地置放在 sp 中,这样cpu在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们定义的栈段当作栈空间来用。
可见,不管我们如何安排 ,cpu 将内存中的某段内存当作代码 ,是因为cs:ip指向了那里;cpu将某段内存当作栈 ,是因为 ss:ip 指向了那里
比如我们将10000h~1001fh安排为代码段,并在里面存储如下代码:
设置cs=1000h,ip=0,这段代码将得到执行。
可以看到,在这段代码中,我们又将10000h~1001fh 安排为栈段和数据段。
10000h~1001fh这段内存,既是代码段,又是栈段和数据段。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。
关键在于cpu中寄存器的设置,即:cs、ip、ss、sp、ds的指向。
段前缀
在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”或“es:”
应用
场景1
问题:将内存ffff:0~ffff:b段元中的数据拷贝到 0:200~0:20b单元中。
分析
(1) 0:200~0:20b单元等同于0020:0~0020:b单元,它们描述的是同一段内存空间
(2)拷贝的过程应用循环实现,简要描述如下:
- 初始化:x=0 循
- 环12次
将ffff:x单元中的数据送入0020:x(需要用一个寄存器中转)
x=x+1
(3)在循环中,源单元ffff:x和目标单元的0020:x的偏移地址x是变量。我们用bx来存放
(4)我们用将0:200~0:20b用0020:0~0020:b描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。
问题
因源单元ffff:x和目标单元0020:x 相距大于64kb,在不同的64kb段里,程序中,每次循环要设置两次ds。
这样做是正确的,但是效率不高。
解决
我们可以使用两个段寄存器分别存放源单元ffff:x和目标单元0020:x的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。
改进的程序中,使用 es 存放目标空间0020:0~0020:b的段地址,用ds存放源空间ffff:0~ffff:b的段地址。
在访问内存单元的指令“mov es:[bx],al”中 ,显式地用段前缀 “es:” 给出单元的段地址,这样就不必在循环中重复设置ds
场景2
debug和汇编编译器masm对指令的不同处理
debug中
- mov ax,[0]
- 表示将ds:0处的数据送入al中。
在汇编源程序中,指令“mov ax,[0]”被编译器当作指令“mov ax,0”处理。
解决:利用段前缀显性的指出段地址
以上就是汇编基础教程段的定义应用详解的详细内容,更多关于汇编基础教程段的资料请关注其它相关文章!
上一篇: 哈啰出行怎么删除历史订单?哈啰出行删除历史订单教程
下一篇: RSS的语言编码大全
推荐阅读
-
详解iOS应用中自定义UIBarButtonItem导航按钮的创建方法
-
详解iOS应用中自定义UIBarButtonItem导航按钮的创建方法
-
汇编基础教程段的定义应用详解
-
详解c#Winform程序的toolStripButton自定义背景应用示例源码
-
javascript享元模式的定义和如何应用代码实例详解
-
javascript享元模式的定义和如何应用代码实例详解
-
php array_walk 对数组中的每个元素应用用户自定义函数详解
-
详解c#Winform程序的toolStripButton自定义背景应用示例源码
-
php array_walk 对数组中的每个元素应用用户自定义函数详解
-
汇编基础教程段的定义应用详解