[汇编]《汇编语言》第3章 寄存器(内存访问)
<h2 id="王爽汇编语言第四版-超级笔记">王爽《汇编语言》第四版 超级笔记</h2><p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>王爽《汇编语言》第四版 超级笔记<ul><li>第3章 寄存器(内存访问)<ul><li>3.1 内存中的存储:字(word)</li><li>3.2 DS 和 </li><li>3.3 字的传送</li><li>3.4 mov、add、sub 指令</li><li>3.5数据段</li><li>3.6 栈、CPU提供的栈机制</li><li>3.7 栈顶超界的问题</li><li>3.8 push、pop 指令</li><li>3.9 栈 段</li></ul></li></ul></li></ul></div><p></p>
<h3 id="第3章-寄存器内存访问">第3章 寄存器(内存访问)</h3>
<h4 id="31-内存中的存储字word">3.1 内存中的存储:字(word)</h4>
<p>CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。</p>
<p>在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813000648796-1972291557.png" alt="image" loading="lazy"></p>
<p>在图3.1中,我们用0、1两个内存单元存放数据 20000(4E20H)。</p>
<p>0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。</p>
<p>对于这个字单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在1号单元中。</p>
<p>同理,将2、3号单元看作一个字单元,它的起始地址为2,在这个字单元中存放数18(0012H),则在2号单元中存放低位字节12H,在3号单元中存放高位字节00H。</p>
<p>我们提出字单元的概念:字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。</p>
<p>高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。</p>
<p>任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。</p>
<h4 id="32-ds-和-address">3.2 DS 和 </h4>
<p>CPU要读写一个内存单元的时候,必须先给岀这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。</p>
<p>8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取10000H单元的内容,可以用如下的程序段进行。</p>
<blockquote>
<p>mov bx,1000H<br>
mov ds,bx<br>
mov al,</p>
</blockquote>
<p>上面的3条指令将10000H(1000:0)中的数据读到al中。</p>
<p>下面详细说明指令的含义:</p>
<blockquote>
<p>mov al,</p>
</blockquote>
<p>前面我们使用mov指令,可完成两种传送:</p>
<p>①将数据直接送入寄存器;<br>
②将一个寄存器中的内容送入另一个寄存器。</p>
<p>也可以使用mov指令将一个内存单元中的内容送入一个寄存器中。</p>
<p>从哪一个内存单元送到哪一个寄存器中呢?</p>
<p>在指令中必须指明。寄存器用寄存器名来指明,内存单元则需用内存单元的地址来指明。</p>
<p>显然,此时mov指令的格式应该是:mov 寄存器名,内存单元地址。</p>
<p>“[•••]”表示一个内存单元,“[•••]”中的0表示内存单元的偏移地址。</p>
<p>我们知道, 只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是多少呢?</p>
<p>指令执行时,8086CPU自动取ds中的数据为内存单元的段地址。</p>
<p>如何把一个数据送入寄存器呢?</p>
<p>我们以前用类似“mov ax,1”这样的指令来完成,从理论上讲,我们可以用相似的方式:mov ds,1000H,来将1000H送入ds。</p>
<p>可是,现实并非如此,<strong>8086CPU不支持将数据直接送入段寄存器的操作</strong>,ds是一个段寄存器,所以 mov ds,1000H 这条指令是非法的。</p>
<p>那么如何将1000H送入ds呢?</p>
<p>只好用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器如bx,再将bx中的内容送入ds。</p>
<p>为什么8086CPU不支持将数据直接送入段寄存器的操作?</p>
<p>这属于8086CPU硬件设计的问题,我们只要知道这一点就行了。</p>
<p>怎样将数据从寄存器送入内存单元?</p>
<p>从内存单元到寄存器的格式是:“mov 寄存器名, 内存单元地址”,从寄存器到内存单元则是:“mov 内存单元地址,寄存器名”。</p>
<p>10000H 可表示为1000:0,用ds存放段地址1000H,偏移地址是0,则mov ,al 可完成从al到10000H的数据传送。</p>
<p>完整的几条指令是:</p>
<blockquote>
<p>mov bx,1000H<br>
mov ds,bx<br>
mov ,al</p>
</blockquote>
<h4 id="33-字的传送">3.3 字的传送</h4>
<p>8086CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性传送一个字。</p>
<p>只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。 比如:</p>
<blockquote>
<p>mov bx,1000H<br>
mov ds,bx<br>
mov ax, ;1000:0处的字型数据送入ax<br>
mov ,ex ;cx中的16位数据送到1000:0处</p>
</blockquote>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813000828163-538049151.png" alt="image" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813000839789-1065886945.png" alt="image" loading="lazy"></p>
<p>内存中的情况如图3.3所示,写出下面的指令执行后内存中的值,思考后看分析。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813000918640-977876016.png" alt="image" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813000926912-1264258099.png" alt="image" loading="lazy"></p>
<h4 id="34-movaddsub-指令">3.4 mov、add、sub 指令</h4>
<p>mov、add、sub指令,它们都带有两个操作对象。</p>
<p>到现在,我们知道,mov指令可以有以下几种形式。</p>
<blockquote>
<p>mov 寄存器,数据 比如:mov ax,8<br>
mov 寄存器,寄存器 比如:mov ax,bx<br>
mov 寄存器,内存单元 比如:mov ax,<br>
mov 内存单元,寄存器 比如:mov ,ax<br>
mov 段寄存器,寄存器 比如:mov ds,ax</p>
</blockquote>
<p>我们可以根据这些己知指令进行下面的推测。</p>
<p>(1)既然有“mov 段寄存器,寄存器”,从寄存器向段寄存器传送数据,那么也应该有“mov 寄存器,段寄存器”,从段寄存器向寄存器传送数据。</p>
<p>一个合理的设想是: 8086CPU内部有寄存器到段寄存器的通路,那么也应该有相反的通路。</p>
<p>有了推测,我们还要验证一下。进入Debug,用A命令,如图3.4所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001011771-272304837.png" alt="image" loading="lazy"></p>
<p>图3.4中,用A命令在一个预设的地址0B39:0100处,用汇编的形式mov ax,ds写入指令,再用T命令执行,可以看到执行的结果,段寄存器ds中的值送到了寄存器ax中。 通过验证我们知道,“mov 寄存器,段寄存器”是正确的指令。</p>
<p>(2)既然有“mov 内存单元,寄存器”,从寄存器向内存单元传送数据,那么也应该有“mov 内存单元,段寄存器”,从段寄存器向内存单元传送数据。</p>
<p>比如我们可以将段寄存器cs中的内容送入内存10000H处,指令如下。</p>
<blockquote>
<p>mov ax,1000H<br>
mov ds,ax<br>
mov ,cs</p>
</blockquote>
<p>在Debug中进行试验,如图3.5所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001052556-1191247391.png" alt="image" loading="lazy"></p>
<p>图3.5中,当CS:IP指向0B39:0105的时候,Debug显示当前的指令mov ,cs,因为这是一条访问内存的指令,Debug还显示出指令要访问的内存单元中的内容。</p>
<p>由于指令中的CS是一个16位寄存器,所以耍访问(写入)的内存单元是一个字单元,它的偏移地址为0,段地址在ds中,Debug在屏幕右边显示岀“DS:0000=0000”,我们可以知道这个字单元中的内容为0。</p>
<p>mov ,cs执行后,CS中的数据(0B39H)被写入1000:0处,1000:1单元存放0BH,1000:0单元存放39H。</p>
<p>最后,用D命令从1000:0开始查看指令执行后内存中的情况,注意1000:0、1000:1两个单元的内容。</p>
<p>(3)“mov 段寄存器,内存单元”也应该可行,比如我们可以用10000H处存放的字型数据设置ds(即将10000H处存放的字型数据送入ds),指令如下。</p>
<blockquote>
<p>mov ax,1000H<br>
mov ds,ax<br>
mov ds,</p>
</blockquote>
<p>可以自行在Debug中进行试验。</p>
<p>add和sub指令同mov一样,都有两个操作对象。</p>
<p>它们也可以有以下几种形式。</p>
<blockquote>
<p>add 寄存器,数据 比如:add ax,8</p>
<p>add 寄存器,寄存器 比如:add ax,bx</p>
<p>add 寄存器,内存单元 比如:add ax,</p>
<p>add 内存单元,寄存器 比如:add ,ax</p>
<p>sub 寄存器,数据 比如:sub ax,9</p>
<p>sub 寄存器,寄存器 比如:sub ax,bx</p>
<p>sub 寄存器,内存单元 比如:sub ax,</p>
<p>sub 内存单元,寄存器 比如:sub ,ax</p>
</blockquote>
<p>它们可以对段寄存器进行操作吗?</p>
<p>比如“add ds,ax”,请自行在Debug中试验。</p>
<h4 id="35--数据段">3.5数据段</h4>
<p>对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。</p>
<p>我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。</p>
<p>比如用123B0H~123B9H这段内存空间来存放数据,我们就可以认为,123B0H~123B9H这段内存是一个数据段,它的段地址为123BH,长度为10个字节。</p>
<p>如何访问数据段中的数据呢?</p>
<p>将一段内存当作数据段,是我们在编程时的一种安排,可以在具体操作的时候,用ds存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。</p>
<p>比如,将123E0H~123B9H的内存单元定义为数据段。现在要累加这个数据段中的前3个单元中的数据,代码如下。</p>
<blockquote>
<p>mov ax,123BH</p>
<p>mov ds,ax ;将123BH送入ds中,作为数据段的段地址</p>
<p>mov al,0 ;用al存放累加结果</p>
<p>add al, ;将数据段第一个单元(偏移地址为0)中的数值加到al中</p>
<p>add al, ;将数据段第二个单元(偏移地址为1)中的数值加到al中</p>
<p>add al, ;将数据段第三个单元(偏移地址为2)中的数值加到al中</p>
</blockquote>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001124255-1650853841.png" alt="image" loading="lazy"></p>
<blockquote>
<p><strong>3.1~3.5 小 结</strong></p>
<p>(1)字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。</p>
<p>(2)用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。</p>
<p>(3)表示一个偏移地址为addiess的内存单元。</p>
<p>(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。</p>
<p>(5)mov、addx sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。</p>
<p>(6)可以根据自己的推测,在Debug中实验指令的新格式。</p>
</blockquote>
<h4 id="36-栈cpu提供的栈机制">3.6 栈、CPU提供的栈机制</h4>
<p>在这里,我们对栈的研究仅限于这个角度:栈是一种具有特殊的访问方式的存储空间。</p>
<p>它的特殊性就在于,最后进入这个空间的数据,最先出去。</p>
<p>可以用一个盒子和3本书来描述栈的这种操作方式。</p>
<p>一个开口的盒子就可以看成一个栈空间,现在有3本书,《高等数学》、《C语言》、《软件工程》,把它们放到盒子中,操作的过程如图3.7所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001146357-964340900.png" alt="image" loading="lazy"></p>
<p>现在的问题是,一次只允许取一本,我们如何将3本书从盒子中取岀来?</p>
<p>显然,必须从盒子的最上边取。</p>
<p>这样取出的顺序就是:《软件工程》、《C语言》、《高等数学》,和放入的顺序相反,如图3.8所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001212657-1765914213.png" alt="image" loading="lazy"></p>
<p>从程序化的角度来讲,应该有一个标记,这个标记一直指示着盒子最上边的书。</p>
<p>如果说,上例中的盒子就是一个栈,我们可以看出,栈有两个基本的操作:入栈和出栈。</p>
<p>入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。</p>
<p>栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)。</p>
<p>现今的CPU中都有栈的设计,8086CPU也不例外。</p>
<p>8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,在基于8086CPU编程的时候,可以将一段内存当作栈来使用。</p>
<p>8086CPU提供入栈和岀栈指令,最基本的两个是PUSH(入栈)和 POP(岀栈)。</p>
<p>比如,push ax 表示将寄存器ax中的数据送入栈中,pop ax 表示从栈顶取出数据送入ax。</p>
<p>8086CPU的入栈和出栈操作都是以字为单位进行的。</p>
<p>下面举例说明,我们可以将10000H〜1000FH这段内存当作栈来使用。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001226643-1801640999.png" alt="image" loading="lazy"></p>
<blockquote>
<p>mov ax,0123H<br>
push ax<br>
mov bx,2266H<br>
push bx<br>
mov ex,1122H<br>
push ex<br>
pop ax<br>
pop bx<br>
pop ex</p>
</blockquote>
<p><strong>注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位。</strong></p>
<p>push ax的执行,由以下两步完成。</p>
<p>1、SP=SP-2, SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;</p>
<p>2、将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。</p>
<p>pop ax的执行过程和push ax刚好相反,由以下两步完成。</p>
<p>1、将SS:SP指向的内存单元处的数据送入ax中;</p>
<p>2、SP=SP+2, SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。</p>
<p>图3.10描述了8086CPU对push指令的执行过程。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001320162-128001799.png" alt="image" loading="lazy"></p>
<p>从图中我们可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。</p>
<h4 id="37-栈顶超界的问题">3.7 栈顶超界的问题</h4>
<p>8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。</p>
<p>但还有一个问题需要讨论,就是SS和SP只是记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。</p>
<p>可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?</p>
<p>图3.13描述了在执行push指令后,栈顶超岀栈空间的情况。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001411477-336323717.png" alt="image" loading="lazy"></p>
<p>图3.13中,将10010H—1001FH当作栈空间,该栈空间容量为16字节(8字),初始状态为空,SS=1000H、SP=0020H,SS:SP指向10020H;</p>
<p>在执行8次push ax后,向栈中压入8个字,栈满,SS:SP指向10010H;</p>
<p>再次执行push ax:sp=sp-2,SS:SP指向1000EH,栈顶超岀了栈空间,ax中的数据送入1000EH单元处,将栈空间外的数据覆盖。</p>
<p>图3.14描述了在执行pop指令后,栈顶超出栈空间的情况。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202108/1162354-20210813001421976-1292049835.png" alt="image" loading="lazy"></p>
<p>图3.14中,将10010H—1001FH当作栈空间,该栈空间容量为16字节(8字),当前状态为满,SS=1000H、SP=0010H,SS:SP指向10010H;</p>
<p>在执行8次pop ax后,从栈中弹出8个字,栈空,SS:SP指向10020H;</p>
<p>再次执行pop ax:sp=sp+2,SS:SP指向10022H,栈顶超出了栈空间。此后,如果再执行push指令,10020H、10021H中的数据将被覆盖。</p>
<p>上面描述了执行push、pop指令时,发生的栈顶超界问题。</p>
<p>可以看到,当栈满的时候再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。</p>
<p>栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。</p>
<p>但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。</p>
<p>我们当然希望CPU可以帮我们解决这个问题,比如说在CPU中有记录栈顶上限和栈底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU在执行push指令的时候靠检测栈顶上限寄存器、在执行pop指令的时候靠检测栈底寄存器保证不会超界。</p>
<p>不过,对于8086CPU,这只是我们的一个设想(我们当然可以这样设想,如果CPU是我们设计的话,这也就不仅仅是一个设想)。</p>
<p>实际的情况是,8086CPU中并没有这样的寄存器。</p>
<p>8086CPU不保证我们对栈的操作不会超界。</p>
<p>这也就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道我们安排的栈空间有多大。</p>
<p>这点就好像CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道要执行的指令有多少。</p>
<p>从这两点上我们可以看出8086CPU的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令是哪一条。</p>
<p>我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;</p>
<p>执行岀栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。</p>
<h4 id="38-pushpop-指令">3.8 push、pop 指令</h4>
<p>push和pop指令是可以在寄存器和内存(栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。)之间传送数据的。</p>
<p>push和pop指令的格式可以是如下形式:</p>
<p>push 寄存器 ;将一个寄存器中的数据入栈<br>
pop 寄存器 ;出栈,用一个寄存器接收出栈的数据</p>
<p>当然也可以是如下形式:</p>
<p>push 段寄存器 ;将一个段寄存器中的数据入栈</p>
<p>pop 段寄存器 ;出栈,用一个段寄存器接收出栈的数据</p>
<p>push和pop也可以在内存单元和内存单元之间传送数据,我们可以:</p>
<p>push 内存单元 ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)</p>
<p>pop 内存单元 ;出栈,用一个内存字单元接收出栈的数据</p>
<p>比如:</p>
<blockquote>
<p>mov ax,1000H<br>
mov ds,ax ;内存单元的段地址要放在ds中<br>
push ;将1000:0处的字压入栈中<br>
pop ;出栈,出栈的数据送入1000:2处</p>
</blockquote>
<p>指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。</p>
<p>push ax是入栈指令,它将在栈顶之上压入新的数据。</p>
<p>一定要注意:它的执行过程是,先将记录栈顶偏移地址的SP寄存器中的内容减2,使得SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向的新的栈顶单元。</p>
<p>push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。</p>
<p>同时,push和pop指令还要改变SP中的内容。我们要十分清楚的是,push和pop指令同mov指令不同,<strong>CPU执行mov指令只需一步操作</strong>,就是传送,而执行push、pop指令却需要两步操作。</p>
<p><strong>执行push时,CPU的两步操作是:先改变SP,后向SS:SP处传送。</strong></p>
<p><strong>执行pop时,CPU的两步操作是:先读取SS:SP处的数据,后改变SP。</strong></p>
<p>注意,push、pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH。</p>
<p>提供:SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。这就是8086CPU提供的栈操作机制。</p>
<blockquote>
<p><strong>栈的综述</strong></p>
<p>(1) 8086CPU提供了栈操作机制,方案如下。</p>
<p>在SS、SP中存放栈顶的段地址和偏移地址;</p>
<p>提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。</p>
<p>(2)push指令的执行步骤:①SP=SP-2;②向SS:SP指向的字单元中送入数据。</p>
<p>(3)pop指令的执行步骤:①从SS:SP指向的字单元中读取数据;②SP=SP+2。</p>
<p>(4)任意时刻,SS:SP指向栈顶元素。</p>
<p>(5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。</p>
<p>(6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。</p>
<p>(7)push、pop实质上是一种内存传送指令,注意它们的灵活应用。</p>
<p>栈是一种非常重要的机制,一定要深入理解,灵活掌握。</p>
</blockquote>
<h4 id="39-栈-段">3.9 栈 段</h4>
<p>对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。</p>
<p>我们可以将长度为N(N<=64KB)的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来用,从而定义了一个栈段。</p>
<p>比如,我们将10010H~1001FH这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一个栈段,段地址为1001H,大小为16字节。</p>
<p>将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop等栈操作指令时自动地将我们定义的栈段当作栈空间来访问。</p>
<p>如何使得如push、pop等栈操作指令访问我们定义的栈段呢?</p>
<p>前面我们己经讨论过,就是要将SS:SP指向我们定义的栈段。</p>
<p>任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H。</p>
<p>一个栈段最大可以设为多少?为什么?</p>
<p>首先从栈操作指令所完成的功能的角度上来看,push、pop等指令在执行的时候只修改SP,所以栈顶的变化范围是0~FFFFH,从栈空时候的SP=0,一直压栈,直到栈满时SP=0;</p>
<p>如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。所以一个栈段的容量最大为64KB。</p>
<p>我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。</p>
<blockquote>
<p>我们可以用一个段存放数据,将它定义为“数据段”;</p>
<p>我们可以用一个段存放代码,将它定义为“代码段”;</p>
<p>我们可以用一个段当作栈,将它定义为“栈段”。</p>
</blockquote>
<p>我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:</p>
<p><strong>对于数据段</strong>,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问;</p>
<p><strong>对于代码段</strong>,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;</p>
<p><strong>对于栈段</strong>,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。</p>
<p><strong>CPU将内存中的某段内容当作代码,是因CS:IP指向了那里;</strong></p>
<p><strong>CPU将某段内存当作栈,是因为SS:SP指向了那里。</strong></p>
<p>我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。</p>
<p>一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。</p>
<p><strong>关键在于CPU中寄存器的设置,即CS、IP,SS、SP,DS的指向。</strong></p>
<p>要非常清楚CPU的工作机理,才能在控制CPU按照我们的安排运行的时候做到游刃有余。</p><br><br>
来源:https://www.cnblogs.com/jpSpaceX/p/15116958.html 楼主的笔记太强了! 第三章的内存寻址和栈操作确实是把双刃剑,搞懂了后面学中断调用会轻松很多。
建议刚接触的朋友重点啃透 DS 与 的搭配,还有 push/pop 指令对栈顶指针的影响——当初我就是卡在“栈顶超界”这个坑里,看了楼主画的图和归纳才豁然开朗。
要是能再补充几个 debug 跟踪栈段内存变化的截图就更完美了,期待后续笔记!
頁:
[1]