道易有道 發表於 2021-11-4 16:52:25

汇编基础教程段的定义应用详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>段</li><li>种类</li><ul class="second_class_ul"><li>代码段</li><li>数据段</li><li>DS和</li><li>栈段</li><ul class="third_class_ul"><li>栈</li><li>操作方式</li><li>8086CPU提供入栈和出栈指令</li><li>栈顶超界的问题</li></ul><li>push、pop指令</li><ul class="third_class_ul"><li>PUSH(入栈)</li><li>POP(出栈)</li></ul><li>栈段定义</li><ul class="third_class_ul"></ul><li>思考</li><ul class="third_class_ul"></ul></ul><li>访问</li><ul class="second_class_ul"></ul><li>段前缀</li><ul class="second_class_ul"><li>场景1</li><ul class="third_class_ul"></ul><li>场景2</li><ul class="third_class_ul"></ul></ul></ul></div><p class="maodian"></p><h2>段</h2>
<p style="text-align: center"><img class="has" alt="" width="595" height="318" src="https://img.jbzj.com/file_images/article/202111/2021110416241650.png" /></p>
<p>将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元</p>
<p class="maodian"></p><h2>种类</h2>
<p class="maodian"></p><h3>代码段</h3>
<p>定义</p>
<p>对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。</p>
<p>可以将长度为 N( N≤64KB )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。</p>
<p>例如</p>
<p style="text-align: center"><img class="has" alt="" width="362" height="143" src="https://img.jbzj.com/file_images/article/202111/2021110416241651.png" /></p>
<p>这段长度为 10 字节的字节的指令,存在从123B0H~123B9H的一组内存单元中,我们就可以认为,123B0H~123B9H这段内存单元是用来存放代码的 ,是一个代码段 ,它的段地址为123BH,长度为10字节。</p>
<p>如何使得代码段中的指令被执行</p>
<ul>
    <li>CPU 只认被 CS:IP 指向的内存单元中的内容为指令。</li>
    <li>所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。</li>
    <li>CS = 123BH,IP = 0000H。</li>
</ul>
<p class="maodian"></p><h3>数据段</h3>
<p>定义</p>
<p>我们可以将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。</p>
<p>比如我们用123B0H~123B9H这段空间来存放数据:</p>
<ul>
    <li>段地址:123BH</li>
    <li>长度:10字节</li>
</ul>
<p>将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。</p>
<p class="maodian"></p><h3>DS和</h3>
<p>我们要读取10000H单元的内容</p>
<p>CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址</p>
<ul>
    <li>在8086PC中,内存地址由段地址和偏移地址组成。</li>
    <li>8086CPU中有一个 DS寄存器,通常用来存放要访问的数据的段地址。</li>
</ul>
<p>将10000H(1000:0)中的数据读到al中。</p>
<blockquote>
<p>mov bx,1000H</p>
<p>mov ds,bx</p>
<p>mov al,</p>
</blockquote>
<p>mov 指令可以将一个内存单元中的内容送入一个寄存器。</p>
<p>mov指令的格式:</p>
<ul>
    <li>mov 寄存器名,内存单元地址</li>
    <li>“[…]”表示一个内存单元, “[…]”中的0表示内存单元的偏移地址。</li>
</ul>
<p>如何用mov指令从10000H中读取数据?</p>
<ul>
    <li>10000H表示为1000:0(段地址:偏移地址)</li>
    <li>将段地址1000H放入ds</li>
    <li>用mov al,完成传送(mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中)</li>
</ul>
<p>如何把1000H送入ds?</p>
<ul>
    <li>8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。(硬件设计的问题)</li>
    <li>mov ds,1000H 是非法的。</li>
    <li>数据&gt;&gt;一般的寄存器&gt;&gt;段寄存器</li>
</ul>
<p>例如</p>
<p>我们将123B0H~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,</p>
<p>代码如下:</p>
<p style="text-align: center"><img class="has" alt="" width="539" height="145" src="https://img.jbzj.com/file_images/article/202111/2021110416241652.png" /></p>
<p class="maodian"></p><h3>栈段</h3>
<p class="maodian"></p><h4>栈</h4>
<p>栈空间当然也是内存空间一部分,它只是一段可以以一种特殊方式进行访问的内存空间。</p>
<p class="maodian"></p><h4>操作方式</h4>
<p>栈有两个基本的操作</p>
<p>入栈:将一个新的元素放到栈顶</p>
<p>出栈:从栈顶取出一个元素</p>
<p>栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出</p>
<p>栈的操作规则:LIFO(Last In First Out,后进先出)</p>
<p>8086CPU的入栈和出栈操作都是以字为单位进行的。</p>
<p>注意:字型数据用两个单元存放,高地址单元放高 8 位,低地址单元放低8 位。</p>
<p class="maodian"></p><h4>8086CPU提供入栈和出栈指令</h4>
<p>&nbsp;SS:SP  </p>
<p>8086CPU中,有两个寄存器:</p>
<ul>
    <li>段寄存器SS  存放栈顶的段地址</li>
    <li>寄存器SP  存放栈顶的偏移地址</li>
</ul>
<p>任意时刻,SS:SP指向栈顶元素。</p>
<p>SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。</p>
<p class="maodian"></p><h4>栈顶超界的问题</h4>
<p>种类</p>
<ul>
    <li>当栈满的时候再使用push指令入栈,</li>
    <li>栈空的时候再使用pop指令出栈,</li>
</ul>
<p>8086CPU不保证对栈的操作不会超界。</p>
<p>8086CPU 只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。</p>
<p>8086CPU的工作机理,只考虑当前的情况:</p>
<ul>
    <li>当前栈顶在何处;</li>
    <li>当前要执行的指令是哪一条。</li>
</ul>
<p>解决办法</p>
<ul>
    <li>要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界</li>
    <li>执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。</li>
</ul>
<p class="maodian"></p><h3>push、pop指令</h3>
<p>定义</p>
<p>push和pop指令是可以在寄存器和内存之间传送数据的。</p>
<p class="maodian"></p><h4>PUSH(入栈)</h4>
<p>push ax:将寄存器ax中的数据送入栈中;</p>
<p>执行过程 push ax</p>
<p>(1)SP=SP–2;</p>
<p>(2)将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。</p>
<p>图示</p>
<p style="text-align: center"><img class="has" alt="" width="521" height="276" src="https://img.jbzj.com/file_images/article/202111/2021110416241653.png" /></p>
<p>入栈时,栈顶从高地址向低地址方向增长</p>
<p class="maodian"></p><h4>POP(出栈)</h4>
<p>pop ax :从栈顶取出数据送入ax。执行过程 (1)将SS:SP指向的内存单元处的数据送入ax中;(2)SP = SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。</p>
<p>图示</p>
<p style="text-align: center"><img class="has" alt="" width="518" height="250" src="https://img.jbzj.com/file_images/article/202111/2021110416241754.png" />&nbsp;</p>
<p>注意:</p>
<p>出栈后,SS:SP指向新的栈顶 1000EH,pop操作前的栈顶元素,1000CH 处的2266H 依然存在 ,但是,它已不在栈中。</p>
<p>当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。</p>
<p>格式</p>
<p>(1)</p>
<p>push 寄存器:将一个寄存器中的数据入栈</p>
<p>pop 寄存器:出栈,用一个寄存器接收出栈的数据</p>
<p>例如:push axpop bx</p>
<p>(2)</p>
<p>push 段寄存器:将一个段寄存器中的数据入栈 pop</p>
<p>段寄存器:出栈,用一个段寄存器接收出栈的数据</p>
<p>例如:push dspop es</p>
<p>(3)</p>
<p>push 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)</p>
<p>pop 内存单元:出栈,用一个内存字单元接收出栈的数据</p>
<p>例如:push pop </p>
<p>指令执行时 ,CPU 要知道内存单元的地址,可以在 push、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。</p>
<p>注意</p>
<p>与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。</p>
<p>CPU执行mov指令只需一步操作,就是传送,而执行push、pop指令却需要两步操作</p>
<p>执行push时:先改变SP,后向SS:SP处传送。</p>
<p>执行pop时: 先读取SS:SP处的数据,后改变SP。</p>
<p>push、pop 等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH。</p>
<p>提供:SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。</p>
<p class="maodian"></p><h3>栈段定义</h3>
<ul>
    <li>可以根据需要 ,将一组内存单元定义为一个段</li>
    <li>比如我们将10010H~1001FH 这段长度为 16 字节的内存空间当作栈来用,以栈的方式进行访问。</li>
    <li>我们可以将长度为 N(N ≤64K )的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段</li>
    <li>这段空间就可以成为栈段,段地址为1000H,大小为16字节</li>
    <li>如何使的如push、pop 等栈操作指令访问我们定义的栈段</li>
    <li>将SS:SP指向我们定义的栈段。</li>
</ul>
<p class="maodian"></p><h3>思考</h3>
<p>我们将10000H~1FFFFH这段空间当作栈段 ,SS=1000H ,栈空间大小为64KB ,栈最底部的字单元地址为1000:FFFE。</p>
<p>任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=FFFEH。</p>
<p>栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2。</p>
<p>SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。</p>
<p>任意时刻,SS:SP指向栈顶元素,当栈为空的时候 ,栈中没有元素 ,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元 ,该单元的偏移地址为栈最底部的字单元的偏移地址+2 ,栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H。</p>
<p class="maodian"></p><h2>访问</h2>
<p>对于数据段,将它的段地址放在 DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段来访问;</p>
<p>对于代码段,将它的段地址放在 CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;</p>
<p>对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地置放在 SP 中,这样CPU在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们定义的栈段当作栈空间来用。</p>
<p>可见,不管我们如何安排 ,CPU 将内存中的某段内存当作代码 ,是因为CS:IP指向了那里;CPU将某段内存当作栈 ,是因为 SS:IP 指向了那里</p>
<p>比如我们将10000H~1001FH安排为代码段,并在里面存储如下代码:</p>
<p style="text-align: center"><img class="has" alt="" width="350" height="266" src="https://img.jbzj.com/file_images/article/202111/2021110416241755.png" /></p>
<p>设置CS=1000H,IP=0,这段代码将得到执行。</p>
<p>可以看到,在这段代码中,我们又将10000H~1001FH 安排为栈段和数据段。</p>
<p>10000H~1001FH这段内存,既是代码段,又是栈段和数据段。</p>
<p>一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。</p>
<p>关键在于CPU中寄存器的设置,即:CS、IP、SS、SP、DS的指向。</p>
<p class="maodian"></p><h2>段前缀</h2>
<p>在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”或“es:”</p>
<p>应用</p>
<p class="maodian"></p><h3>场景1</h3>
<p>问题:将内存ffff:0~ffff:b段元中的数据拷贝到 0:200~0:20b单元中。</p>
<p>分析</p>
<p>(1) 0:200~0:20b单元等同于0020:0~0020:b单元,它们描述的是同一段内存空间</p>
<p>(2)拷贝的过程应用循环实现,简要描述如下:</p>
<ul>
    <li>初始化:X=0 循</li>
    <li>环12次</li>
</ul>
<p>将ffff:X单元中的数据送入0020:X(需要用一个寄存器中转)</p>
<p>X=X+1</p>
<p>(3)在循环中,源单元ffff:X和目标单元的0020:X的偏移地址X是变量。我们用bx来存放</p>
<p>(4)我们用将0:200~0:20b用0020:0~0020:b描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。</p>
<p>问题</p>
<p>因源单元ffff:X和目标单元0020:X 相距大于64KB,在不同的64KB段里,程序中,每次循环要设置两次ds。</p>
<p>这样做是正确的,但是效率不高。</p>
<p>解决</p>
<p>我们可以使用两个段寄存器分别存放源单元ffff:X和目标单元0020:X的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。</p>
<p style="text-align: center"><img class="has" alt="" width="505" height="247" src="https://img.jbzj.com/file_images/article/202111/2021110416241756.png" /></p>
<p>改进的程序中,使用 es 存放目标空间0020:0~0020:b的段地址,用ds存放源空间ffff:0~ffff:b的段地址。</p>
<p>在访问内存单元的指令“mov es:,al”中 ,显式地用段前缀 “es:” 给出单元的段地址,这样就不必在循环中重复设置ds</p>
<p class="maodian"></p><h3>场景2</h3>
<p>Debug和汇编编译器Masm对指令的不同处理</p>
<p>Debug中</p>
<ul>
    <li>mov ax,</li>
    <li>表示将ds:0处的数据送入al中。</li>
</ul>
<p>在汇编源程序中,指令“mov ax,”被编译器当作指令“mov ax,0”处理。</p>
<p>解决:利用段前缀显性的指出段地址</p>
<p>以上就是汇编基础教程段的定义应用详解的详细内容,更多关于汇编基础教程段的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>汇编语言段定义的使用</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: 汇编基础教程段的定义应用详解