简单地解释堆栈框架的概念

看来,我在编程语言devise中得到了调用堆栈的概念。 但我找不到(可能我只是不够努力地search)什么样的堆栈框架的正确解释。

所以我想请一个人用几句话向我解释一下。

堆栈帧是被压入堆栈的dataframe。 在调用堆栈的情况下,堆栈框架将表示一个函数调用及其参数数据。

如果我没有记错的话,函数返回地址先被压入堆栈,然后是局部variables的参数和空间。 他们一起构成了“框架”,尽pipe这可能是依赖于架构的。 处理器知道每个帧中有多less个字节,并在帧被推出并从堆栈中popup时相应地移动堆栈指针。

编辑:

高级调用栈和处理器的调用栈之间有很大的区别。

当我们谈论处理器的调用堆栈时,我们正在讨论在汇编或机器代码中使用字节/字级别的地址和值。 在讨论高级语言时有“调用堆栈”,但是它们是由运行时环境pipe理的debugging/运行时工具,以便您可以logging程序(高级别)出了什么问题。 在这个级别上,像行号,方法和类名称这样的东西通常是已知的。 当处理器获得代码时,它绝对没有这些东西的概念。

快速包装。 也许有人有一个更好的解释。

调用堆栈由1个或多个堆栈帧组成。 每个堆栈帧对应于一个函数或程序的调用,它还没有以返回结束。

要使用堆栈帧,线程保留两个指针,一个被称为堆栈指针(SP),另一个被称为帧指针(FP)。 SP始终指向堆栈的“顶部”,而FP总是指向框架的“顶部”。 此外,线程还维护一个程序计数器(PC),它指向要执行的下一条指令。

以下内容存储在堆栈中:局部variables和临时对象,当前指令的实际参数(过程,函数等)

关于清洁堆栈有不同的调用约定。

如果你理解栈很好,那么你将会明白程序中的内存是如何工作的,如果你明白程序中的内存是如何工作的,你将会明白函数如何在程序中存储,如果你明白函数如何在程序中存储,你将会理解recursion函数是如何工作的,你明白recursion函数是如何工作的,你将会理解编译器是如何工作的,如果你理解编译器是如何工作的,那么你的思想就像编译器一样工作,你将很容易地debugging

让我解释一下堆栈的工作原理:

首先,你必须知道如何将函数存储在堆栈中:

堆存储dynamic内存分配值。 堆栈存储自动分配和删除值。

在这里输入图像说明

让我们来看看例子:

def hello(x): if x==1: return "op" else: u=1 e=12 s=hello(x-1) e+=1 print(s) print(x) u+=1 return e hello(4) 

现在了解这个程序的一些部分:

在这里输入图像说明

现在让我们看看什么是堆栈和什么是堆栈部分:

在这里输入图像说明

分配堆栈:

记住一件事,如果任何函数得到“返回”,无论它已经加载了所有的本地variables或任何它将立即从栈中返回的将他的栈帧。 这意味着当任何recursion函数得到基本条件,并且在基本条件之后放置返回,那么基本条件将不会等待加载位于程序的“其他”部分中的局部variables,它将立即从栈中返回当前帧,现在如果一帧返回下一帧是在激活logging。 实际看到这个:

在这里输入图像说明

块的取消分配:

所以现在每当一个函数发现return语句,它就从堆栈中删除当前帧。

而从堆栈中返回的值将按照它们在堆栈中分配的顺序的相反顺序返回。

在这里输入图像说明

这些都是非常简短的描述,如果你想知道更多关于堆栈和双recursion的深入阅读这个博客的两个post:

更多关于一步一步堆栈

更多关于双栈recursion与堆栈

“调用堆栈由栈帧组成…” – 维基百科

堆栈框架是你放在堆栈上的东西。 它们是包含有关要调用的子例程的信息的数据结构。

程序员可能对堆栈框架没有广义的认识(它是堆栈中的一个单一的函数调用并保存返回地址,参数和局部variables的单一实体),但是狭义上说 – 当stack frames是在编译器选项的上下文中提到。

这个问题的作者是否有意,但是从编译选项的angular度来看栈结构的概念是一个非常重要的问题,这里没有涉及到其他的答复。

例如,Microsoft Visual Studio 2015 C / C ++编译器具有以下与stack frames相关的选项:

  • / Oy(帧指针省略)

GCC有以下几点:

  • -fomit-frame-pointer(不要将帧指针保存在一个不需要的函数的寄存器中,这样可以避免指令保存,设置和恢复帧指针;还可以在许多函数中使用额外的寄存器)

英特尔C ++编译器具有以下内容:

  • -fomit-frame-pointer(确定EBP在优化中是否被用作通用寄存器)

其中有以下别名:

  • / Oy公司

Delphi有以下命令行选项:

  • – $ W +(生成堆栈框架)

从编译器的angular度来看,从这个特定的意义上来说,一个栈框架就是这个例程入口和出口代码 ,它将一个锚点推入堆栈 – 也可以用于debugging和exception处理。 debugging工具可以扫描堆栈数据,并使用这些锚点进行回溯,同时在堆栈中查找call sites ,即按照分层调用的顺序显示函数的名称。 对于英特尔架构,它是push ebp; mov ebp, esp push ebp; mov ebp, esp or enter for entry and mov esp, ebp; pop ebp mov esp, ebp; pop ebpleave退出。

这就是为什么程序员理解编译器选项时栈帧是什么是非常重要的 – 因为编译器可以控制是否生成这个代码。

在某些情况下,编译器可以省略堆栈帧(例程的入口和出口代码),variables将直接通过堆栈指针(SP / ESP / RSP)而不是方便的基址指针(BP / ESP / RSP)。 省略堆栈帧的条件,例如:

  • 该函数是叶函数(即不调用其他函数的最终实体);
  • 有没有尝试/最后或尝试/除了或类似的结构,即没有例外被使用;
  • 堆栈上没有传出参数的例程。
  • 该函数没有参数;
  • 该函数没有内联汇编代码;
  • 等等…

省略堆栈帧(例程的入口和出口代码)可以使代码更小更快,但是也可能会对debugging器在堆栈中回溯数据并将其显示给编程人员的能力产生负面影响。 这些编译器选项决定函数在哪些条件下应该具有进入和退出代码,例如:(a)总是,(b)从不,(c)在需要时(指定条件)。

堆栈帧是与函数调用相关的打包信息。 这些信息通常包括传递给函数的参数,局部variables以及终止时返回的位置。 激活logging是堆栈帧的另一个名称。 堆栈框架的布局由制造商在ABI中确定,并且支持ISA的每个编译器必须符合这个标准,但是布局scheme可以依赖于编译器。 通常堆栈帧大小不受限制,但有一个名为“红色/保护区”的概念,允许系统调用等执行而不会干扰堆栈帧。

总是有一个SP,但在一些ABI上(例如ARM和PowerPC)FP是可选的。 需要放在堆栈上的参数可以使用SP来抵消。 是否为函数调用生成堆栈框架取决于参数的types和数量,局部variables以及局部variables的访问方式。 在大多数ISA上,首先使用寄存器,如果有比专用于传递参数的寄存器更多的参数,则将这些寄存器放置在堆栈上(例如,x86 ABI有6个寄存器来传递整数参数)。 因此,有些时候,有些函数不需要堆栈放置在堆栈上,只是将返回地址压入堆栈。