
常用软件类: |
|杀毒安全 | |联络聊天 | |网络软件 | |多媒体类 | |系统工具 | |图形图像 | |系统工具 | |应用软件 | |行业软件 |
开发设计类: |
|动画制作 | |图像处理 | |3D设计 | |操作系统 | |站长学院 | |网络相关 | |WEB设计 | |数据库类 | |程序开发 |
到目前为止,您已经了解了基本的寄存器以及内存的操作知识。事实上,您现在已经可以写出很多的底层数据处理程序了。
下面我来说说堆栈。堆栈实在不是一个让人陌生的数据结构,它是一个 先进后出 (FILO)的线性表,能够帮助你完成很多很好的工作。
先进后出 (FILO)是这样一个概念: 最后 放进表中
的数据在取出时 最先 出来。 先进后出 (FILO)和 先
进先出 (FIFO, 和先进后出的规则相反),以及 随
机存取 是最主要的三种存储器访问方式。
对于堆栈而言,最后放入的数据在取出时最先出
现。对于子程序调用,特别是递归调用来说,这
是一个非常有用的特性。
一个铁杆的汇编语言程序员有时会发现系统提供的寄存器不够。很显然,你可以使用普通的内存操作来完成这个工作,就像C/C++中所做的那样。
没错,没错,可是,如果数据段(数据选择器)以及偏移量发生变化怎么办?更进一步,如果希望保存某些在这种操作中可能受到影响的寄存器的时候怎么办?确实,你可以把他们也存到自己的那片内存中,自己实现堆栈。
太麻烦了……
既然系统提供了堆栈,并且性能比自己写一份更好,那么为什么不直接加以利用呢?
系统堆栈不仅仅是一段内存。由于CPU对它实施管理,因此你不需要考虑堆栈指针的修正问题。可以把寄存器内容,甚至一个立即数直接放到堆栈里,并在需要的时候将其取出。同时,系统并不要求取出的数据仍然回到原来的位置。
除了显式地操作堆栈(使用PUSH和POP指令)之外,很多指令也需要使用堆栈,如INT、CALL、LEAVE、RET、RETF、IRET等等。配对使用上述指令并不会造成什么问题,然而,如果你打算使用LEAVE、RET、RETF、IRET这样的指令实现跳转(比JMP更为麻烦,然而有时,例如在加密软件中,或者需要修改调用者状态时,这是必要的)的话,那么我的建议是,先搞清楚它们做的到底是什么,并且,精确地了解自己要做什么。
正如前面所说的,有两个显式地操作堆栈的指令:
| 助记符 |
功能 |
| PUSH | 将操作数存入堆栈,同时修正堆栈指针 |
| POP | 将栈顶内容取出并存到目的操作数中,同时修正堆栈指针 |
我们现在来看看堆栈的操作。
执行之前

执行代码
| mov ax,1234h mov bx,10 push ax push bx |
之后,堆栈的状态为

之后,再执行
| pop dx pop cx |
堆栈的状态成为

当然,dx、cx中的内容将分别是000ah和1234h。
注意,最后这张图中,我没有抹去1234h和000ah,因为POP指令并不从内存中抹去数值。不过尽管如此,我个人仍然非常反对继续使用这两个数(你可以通过修改SP来再次POP它们),然而这很容易导致错误。
一定要保证堆栈段有足够的空间来执行中断,以及其他一些隐式的堆栈操作。仅仅统计PUSH的数量并据此计算堆栈所需的大小很可能造成问题。
CALL指令将返回地址放到堆栈中。绝大多数C/C++编译器提供了“堆栈检查”这个编译选项,其作用在于保证C程序段中没有忘记对堆栈中多余的数据进行清理,从而保证返回地址有效。
本章中介绍了内存的操作的一些入门知识。限于篇幅,我不打算展开细讲指令,如cmps*,lods*,stos*,等等。这些指令的用法和前面介绍的movs*基本一样,只是有不同的作用而已。