口腔医院网站优化服务商,c .net网站开发视频教程,苏州市现代建设咨询管理公司,济南外贸网站建设公司一#xff0c;栈
#xff08;一#xff09;栈的特点 栈是一种具有特殊访问方式的存储空间#xff0c;特殊在于#xff0c;进出这块存储空间的数据#xff0c;“先进后出#xff0c;后进先出” 由于栈的这个“先进后出”的特点#xff0c;我们可以利用其来很好的操作内…一栈
一栈的特点 栈是一种具有特殊访问方式的存储空间特殊在于进出这块存储空间的数据“先进后出后进先出” 由于栈的这个“先进后出”的特点我们可以利用其来很好的操作内存 在《王爽汇编》中作者用三本书十分形象地来描述了入栈和出栈的操作对栈比较陌生的同学可以仔细理解很理解的同学则可跳过 入栈的方式 出栈的方式 入栈将数据放在栈的顶部 出栈将栈顶部的数据取出 也就是说无论是入栈和出栈当前操作的数据一定是当前栈中最顶部的数据我们假想有一个指针它会一直指着栈顶数据帮助我们定位 二CPU中的栈机制
栈先进后出的这个特点对于我们操作数据是十分方便的一个机制因此CPU中设计好了提供给我们相关的指令用这种机制来访问内存
简而言之我们可以主观地将某块内存视作栈实际上它在内存中是没变的只是我们主观去刻意认为然后用相关指令来操作它 最基本的两个入栈和出栈命令是push入栈pop出栈 注意入栈和出栈的操作都是以字一个字等于两个字节1 word 2 byte为单位的这意味着任何一次出入栈操作的数据一定是十六位的 例 push ax √ push bh ×bh是bx的高八位寄存器 pop cx √ pop dl ×dl是dx的低八位寄存器 ①初始空栈 规定为如果将一块内存视作栈那么这块内存最小的地址处低地址为栈顶反之高地址为栈底例如下图[10000]为栈顶[1000F]为栈底你可以认为只不过是把从左到右由低地址到高地址的一块内存顺时针旋转了90度立起来方便我们看而已 此时栈指针指向[10010]处即栈最底部[1000F]的再下一处因为是空栈 下面将10000~1000F这段内存视作栈并用相关指令来操作它 ②【mov ax,0123】【push ax】 将0123赋值给ax然后把ax的值压入栈中 这里再次涉及到小端存储数据的方式现有的机器基本都是小端存储ax分为ah高位的01和al低位的23 因此高位的01应该放在[1000F]处低位的23放在[1000E]处因此执行完这两条汇编之后栈中的数据应如下图所示 此时栈指针指向[1000E]处栈顶数据是23 ③【mov bx,2266】【push bx】 这一步和②同理高位的22被压入相对高位的[1000D]处低位的66被压入相对低位的[1000C]处 此时栈指针指向[1000C]处栈顶数据是66 ④【mov cx,1122】【push cx】 这一步和②③同理高位的11被压入相对高位的[1000B]处低位的22被压入相对低位的[1000A]处 此时栈指针指向[1000C]处栈顶数据是22 接下来3步我们整合在一起 形如【pop 寄存器】指令的作用将当前栈指针指向的数据放入寄存器中 ⑤【pop ax】栈指针指向[1000A]处因此将66放入al22放入ah别忘了push和pop一定是以字为单位操作数据的而不是字节因此执行完后ax1122 ⑥【pop bx】同理栈指针指向的是[1000C]将66放入bl22放入bhbx2266 ⑦【pop cx】同理栈指针指向的是[1000E]将23放入cl01放入chcx0123 最后栈指针再次归位到[10010]处因为栈空 总结 ①内存中地址从左到右由低到高栈中地址从上到下由低到高可以认为栈不过是一块内存顺时针旋转了90度立起来方便我们看而已 ②假想一个栈指针它会时刻指向栈中目前最顶部的数据 ③push、pop指令操作的是至少是一个字不能单独操作一个字节一个字word两个字节byte ④每使用push指令压入一个字栈指针会向栈顶低地址处移动一个字的大小相当于-2每使用pop指令弹出一个字栈指针会向栈底高地址处移动一个字的大小相当于2 二SS、SP寄存器和push、pop的作用
一SS、SP寄存器 在前文中我们反复提到以下两点 ①栈是我们主观假想出来的具有特殊存储方式的内存与普通内存别无二致 ②假想一个栈指针它会时刻指向栈中目前最顶部的数据 怎么让cpu知道我们把哪块内存视作栈了呢这个假想出来的栈指针要如何表现呢cpu提供给我们两个寄存器解决了这两个问题 回想一下其实就像学习CS、IP寄存器时一样我们当时解决的问题是“汇编指令和普通数据在内存中存储起来后没有差别都是二进制数但是cpu会将[CS:IP]指向的地址视作汇编来执行” 所以现在举一反三又需要两个这样的寄存器一个用来标识段地址一个用来标识偏移地址它们共同指向我们主观视作栈的一块内存的栈顶数据它的地址则是[SS:SP] 因此[SS:SP]就是实际cpu中的栈指针 二push指令 push指令主要有两种用法 ①push 寄存器例如【push ax】 此处必须是十六位寄存器前文已经阐述过 当本条指令被执行之后cpu会将指定寄存器ax的数据赋值给[SS:SP]处即栈顶 执行完后SP寄存器的值会-2因为新压入了两个字节的数据成为了新的栈顶数据栈指针会向低地址偏移两个字节 ②push [偏移地址]例如【push [0011]】 当本条指令被执行之后cpu会将指定物理地址[DS:0011]处不明白为什么是DS的请仔细复习上一篇速通汇编五的数据同样是十六位数据如FFFF赋值给[SS:SP]处即栈顶 执行完后SP寄存器的值会-2因为新压入了两个字节的数据成为了新的栈顶数据栈指针会向低地址偏移两个字节 实例
①先用r命令查看寄存器观察到SS和SP的值然后用d命令查询[SS:SP]处内存的值 ②在[0000:0000]处提前写入数据12ab34cd修改DS的值为0000这样待会push [0]时[0]就代表[0000:0000] ④a命令在[CS:IP]即[073f:0100]处写入汇编 【mov ax,1234】【push ax】 【push [0]】 ⑤执行前两条汇编【mov ax,1234】【push ax】执行完后查看栈的变化 观察到ax中的1234被压入栈中ah的12在高地址al的34在低地址且SP00FD-200FB符合预期
注观察到此处栈顶数据左边的内存数据发生变化这是正常现象无需关心 ⑥执行第3条汇编【push [0]】执行完后查看栈的变化 观察到[0000:0000]处的两个字节12和AB分别被压入栈的低和高地址处且SP00FB-200F9符合预期 三pop指令 pop指令用来弹出栈顶数据弹出后不是说消失了你要找个其它位置存放的 pop指令主要有两种用法 ①pop 寄存器例如【pop ax】 此处必须是十六位寄存器前文已经阐述过 当本条指令被执行之后cpu会将[SS:SP]处即栈顶的数据弹出赋值给指定寄存器ax 执行完后SP寄存器的值会2因为弹出了栈顶数据栈指针会向高地址处偏移2个字节 ②pop [偏移地址]例如【pop [0011]】 当本条指令被执行之后cpu会将[SS:SP]处即栈顶的数据弹出赋值给指定物理地址[DS:0011]处 执行完后SP寄存器的值会2因为弹出了栈顶数据栈指针会向高地址处偏移2个字节 实例
①a命令在在[CS:IP]处写入汇编 【pop ax】【pop [0011]】 ②执行第一条汇编【pop ax】执行完后查看栈与ax的变化 观察到栈顶数据12AB被弹出低位的12放入al中高位的AB放入ah中axAB12
且SP00F9200FB符合预期 ③执行第二条汇编【pop [0011]】执行完后查看栈与[0000:0011]处的变化 观察到栈顶数据3412被弹出低位的34放入低位的[0000:0011]处高位的12放入高位的[0000:0012]处
且SP00FB200FD符合预期 三栈越界相关问题
以下是《王爽汇编》原文节选提到的CPU通指8086CPU
上文已经完全说明CPU由SS和SP来指向栈顶地址并提供push和pop指令来出入栈
有个问题是CPU不会保证我们对栈的操作不超界
也就是说CPU只知道栈顶在何处由[SS:SP]指示而不知道我们安排的栈空间有多大。这点就好像 CPU 只知道当前要执行的指令在何处由CS:IP指示而不知道要执行的指令有多少从这两点上我们可以看出CPU的工作机理它只考虑当前的情况当前的顶在何处、当前要执行的指令是哪一条
我们在编程的时候要自己操心栈顶超界的问题要根据可能用到的最大栈空间来安排栈的大小防止入栈的数据太多而导致的超界执行出栈操作的时候也要注意以防栈空的时候继续出栈而导致的超界