对栈操作的理解

转载 2015年11月18日 13:20:29
对栈操作的理解

一个CPU具有八个通用寄存器:EAX,EDX,ECX,ESI,EDI,EBP,ESP,EBX。这八个通用寄存器中的每一个都被安排了特定用途,CPU在执行某些特定指令时需要特定的寄存器协作以高效的完成其指令执行过程。

其中有两个重要的寄存器负责栈的处理:  EBP(扩展基址指针)和  ESP(扩展栈顶指针)。

除上面的八个通用寄存器,下面还会提到  EIP(扩展指令指针)。


当调用一个函数时:

①:调用程序首先将函数参数压入栈中,从而对函数调用进行设置。

②:将EIP保存到栈上,这样程序在函数返回后就能够在之前离开的地方继续执行。这个地址被称为返回地址。

③:执行call命令,将该函数的地址放入EIP中并执行。


而被调用函数的职责是:

①:将调用程序的EBP寄存器保存到栈上。

②:将当前ESP寄存器保存到EBP寄存器。(设置当前栈帧)

③:将ESP寄存器减小,从而为该函数的本地变量腾出空间。

④:上面的语句执行完毕后,函数才可以执行自己的语句。


被调用函数返回到调用程序之前:

①:将ESP增加到EBP,从而将栈清空。

②:从栈中弹出所保存的EIP。

说了这么多,其实不用知道这些,我们只需要知道函数调用的工作原理就行了。

函数调用的工作原理就是:压栈 - 操作 - 弹栈 ,但是有上面的解释,我们可以更好的理解函数调用的原理。

而压栈的本质就是记录压栈前的状态,弹栈是恢复到压栈前的状态。而这个记录状态的东西(想不出来叫什么,暂且叫它东西吧),这个东西当然要能在弹栈时恢复的跟压栈前一模一样,这样,我们就需要知道在函数执行过程中哪些量变了,哪些量没变,所因为我们没必要将没变的量压入栈中。现在,重点已经不是压栈和弹栈了,重点是如何恢复到函数调用前的那个状态。下面贴上两段代码:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n = 2;
    print(n + 1);
    printf("%d\n",n);
}

int print(int n)
{
    printf("%d\n",n);
//    n++;
    return n;
}

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n = 2;
    {
        n += 1;
        printf("%d\n",n);
//        n++;
        n -= 1;
    }
    printf("%d\n",n);
}

我们先不看注释掉的部分。第一段代码调用了函数来输出,而第二段代码中没有调用函数,只是用{}括起来了一部分代码,两段代码的结果一模一样,而第二段代码没有用栈就实现了第一段代码中用栈的效果,可想而知,效率肯定是比用栈高的。这样做的原理其实很简单,函数调用时的栈操作就是保存调用的参数的信息,并在函数操作结束时还原参数信息。而第二段代码的思想就是在压栈前(printf函数执行前)对“参数”进行更改,并在弹栈后(printf函数执行完后)将“参数”恢复为原来的值。所以可以实现和第一段代码相同的功能。

现在我们可以看看注释的部分,如果加上注释的部分,两段代码的结果就不同了。第一段代码的运行结果并没有改变,但是第二段代码的结果变了。原因是第二段代码在{}内的代码段里,没能将n改回原先的值。

看到这里应该有些小收获吧,把第二段代码改一下就不会出现n不能变回原值的问题了。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int temp,n = 2;
    {
        temp = n;
        printf("%d\n",n);
        n++;
        n = temp;
    }
    printf("%d\n",n);
}

这样子,用temp记录下了n的初始值,接下来在{}内部,n想怎么改就怎么改,不用在担心n不能变回原始值的问题了。

哈啊啊,这样子就体现出了栈的三大特性(这三大特性是我自己总结的,可能并不是很准确。)之一:记录。而其他两大特性:追踪 和 保密 并没有体现出来。

追踪无法体现的原因是:假设还有temp1,temp2,……tempn,由于各个temp并不是连续定义的,所以他们在内存中的地址不连续,如果知道temp1的地址,无法根据temp1找到temp2的地址,以此类推,这样子的话就无法实现连续压栈,连续弹栈的操作。

保密无法体现的原因是:temp定义在这个代码段之外,也就是说temp的访问权限在main函数手里,而不是在这个代码段手里。这样子,main函数中的其他操作也可以更改temp,这样会造成潜在的错误。这里的感觉有点像面向对象的封装,的确是有封装的意思,但是最致命的并不是没有封装,致命的是如果一个栈不能保密的话,访问栈的人就可以获取栈顶的地址,而找到栈顶地址就意味着整个栈的信息都可以通过访问地址访问到,而通过访问地址访问栈的元素就已经脱离了栈的规则,也就是说访问者可以随意修改你的栈,当然也可以让你的栈崩溃。

其他的我就不多说了,我所理解的栈的三大特性:记录,追踪 和 保密 ,以后还会在针对这三点做些文章的。

希望大家看了之后能有收获。

http://my.csdn.net/restlessssh

一个初学者对栈的理解

作业
  • holycipher
  • holycipher
  • 2016年10月10日 22:10
  • 1516

如何对栈进行简单的初级操作

何为栈          栈在数据结构当中称为一种线性表,它限制只能在表的一端进行插入或删除操作。这一端就叫做栈顶,反之另一端则称为栈底。       栈有个明显的特征是后进先出(Last in ...
  • luoweiyou
  • luoweiyou
  • 2017年02月07日 16:41
  • 811

栈与堆理解与区别

栈与堆
  • u013908944
  • u013908944
  • 2017年04月08日 09:47
  • 529

自己谈谈对java堆和栈的理解

最近看了几篇关于java堆和栈的区别的几个帖子。自己对这两个概念有了一定 了理解。         其实java的堆和栈是一种内存资源。这里提到了内存资源,顺便说一 下其实java内存不仅仅是栈...
  • ycy123ycy
  • ycy123ycy
  • 2013年05月21日 19:47
  • 1164

对堆栈的认识

什么是堆和栈,它们在哪儿?问题描述编程语言书籍中经常解释值类型被创建在栈上,引用类型被创建在堆上,但是并没有本质上解释这堆和栈是什么。我仅有高级语言编程经验,没有看过对此更清晰的解释。我的意思是我理解...
  • ljx_5489464
  • ljx_5489464
  • 2016年03月29日 01:51
  • 1156

个人对于堆栈的理解(简单通俗)

最近在做安装包(Inno Setup),也没好好看C++,然后安装包出了点小问题,说是堆溢出,自己也没理解透堆栈这个概念,在这里我就简单的总结一下。   1.堆     在C++ 中,只有程序员自己分...
  • u013059441
  • u013059441
  • 2017年03月08日 11:48
  • 624

什么是DOM(个人理解)

了解DOM需要清楚几个问题: 什么是DOM? DOM可以用来干什么? DOM是怎么来的? 怎么使用DOM?什么是DOM? DOM(document Object Model),是针对HTML和XM...
  • makel12
  • makel12
  • 2016年04月19日 20:06
  • 1982

关于ARM体系中栈的对齐问题

关于ARM体系中栈的对齐问题-汤权 基于ARM架构的处理器的C语言程序设计遵循ATPCS(ARM-THUMB procedure call standard)和AAPCS(ARM Applicati...
  • tq384998430
  • tq384998430
  • 2016年11月23日 09:55
  • 734

sdut oj3335 数据结构实验之栈八:栈的基本操作

题目链接:点击打开链接 数据结构实验之栈八:栈的基本操作 Time Limit: 1000MS Memory limit: 65536K 题目描述 堆栈是一种基本的数据...
  • Annfan123
  • Annfan123
  • 2016年08月02日 14:28
  • 231

复习一下栈的基本操作

题目描述 对输入整数序列1 2 3 ..执行一组栈操作,输出操作的出栈序列。 输入 每行是一个测试用例,表示一个操作序列。操作序列由P和Q两个符号组成,P表示入栈,Q表示...
  • u012942818
  • u012942818
  • 2014年11月15日 11:02
  • 1158
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:对栈操作的理解
举报原因:
原因补充:

(最多只允许输入30个字)