简要说明堆栈框架的概念


200

看来我在编程语言设计中有了调用堆栈的想法。但是我找不到(也许我只是不够努力地)关于什么是堆栈框架的任何体面的解释。

所以我想请某人向我解释一下。

Answers:


195

堆栈帧是被推入堆栈的数据帧。在调用堆栈的情况下,堆栈帧将表示函数调用及其参数数据。

如果我没记错的话,函数的返回地址首先被压入堆栈,然后是局部变量的参数和空间。尽管这可能取决于体系结构,但它们共同构成了“框架”。处理器知道每个帧中有多少字节,并在将帧压入并弹出堆栈时相应地移动堆栈指针。

编辑:

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

当我们谈论处理器的调用堆栈时,我们谈论的是在汇编或机器代码中以字节/字级别使用地址和值。在谈论高级语言时,有“调用堆栈”,但是它们是运行时环境管理的调试/运行时工具,因此您可以(在较高级别)记录程序出了什么问题。在此级别上,通常知道行号,方法和类名之类的东西。到处理器获得代码时,它完全没有这些东西的概念。


6
“处理器知道每个帧中有多少字节,并在将帧压入并弹出堆栈时相应地移动堆栈指针。” -我怀疑处理器对堆栈一无所知,因为我们是通过Subbing(分配),推入和弹出操作来操纵它的。因此,这里有一些调用约定,这些约定解释了我们应该如何使用堆栈。
Victor Polevoy '18

78

如果您非常了解堆栈,那么您将了解内存在程序中的工作方式,如果您了解内存在程序中的工作方式,则将了解函数在程序中的存储方式,如果您了解函数在程序中的存储方式,则将了解递归函数的工作方式以及是否您了解递归函数的工作原理,您将了解编译器的工作原理,如果您了解编译器的工作原理,那么您的想法将像编译器一样工作,并且您将很容易调试任何程序

让我解释一下堆栈是如何工作的:

首先,您必须知道函数如何在堆栈中表示:

堆存储动态分配的值。
堆栈存储自动分配和删除值。

在此处输入图片说明

让我们用例子来理解:

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)

现在了解该程序的各个部分:

在此处输入图片说明

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

在此处输入图片说明

堆栈分配:

记住一件事:如果任何函数的返回条件都得到满足,则无论它是否加载了局部变量,它都会立即从其堆栈框架中返回。这意味着只要任何递归函数都满足基本条件,并且在基本条件后放一个返回值,基本条件就不会等待加载位于程序“其他”部分的局部变量。它将立即从堆栈中返回当前帧,随后下一帧现在处于激活记录中。

在实践中看这个:

在此处输入图片说明

块的解除分配:

因此,现在无论何时函数遇到return语句,它都会从堆栈中删除当前帧。

从堆栈中返回时,将以与原始值相反的顺序返回值。

在此处输入图片说明


3
堆栈向下增长而堆向上增长,则在图中将它们反转。正确的图片在这里
拉斐尔

@Rafael抱歉,我正在谈论增长方向,我不是在谈论堆栈增长的方向。生长方向和堆垛生长方向之间存在差异。看到这里 stackoverflow.com/questions/1677415/...
Aaditya浦

2
拉斐尔是对的。同样,第一张图片是错误的。将其替换为其他内容(在Google图片中搜索“堆堆栈”)。
Nikos

因此,如果我正确理解,在您的第三个图中,有3个堆栈框架,因为hello()已经递归调用了hello(),然后又再次(递归)调用hello()了全局框架,而全局框架是调用第一个hello()
安迪J

1
链接在哪里引导我们?出于安全考虑,这些链接应尽快删除。
Shivanshu

45

快速总结。也许有人有更好的解释。

调用堆栈由1个或多个堆栈帧组成。每个堆栈帧对应于对尚未以返回终止的函数或过程的调用。

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

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

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


7
不要忘记子例程的返回地址在堆栈上。
Tony R

4
帧指针在x86方面也是基本指针
peterchaula

1
我想强调指出,帧指针指向当前活动过程化身的堆栈帧的开头。
服务器Khalilov

13

“调用堆栈由堆栈框架组成...”-  Wikipedia

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


抱歉,我不知道我如何错过Wiki。谢谢。我是否正确理解,在动态语言中,由于无法确切知道函数的局部变量,因此框架的大小不是恒定值吗?
ikostia 2012年

框架的大小和性质在很大程度上取决于机器的体系结构。实际上,调用栈的范例是特定于体系结构的。据我所知,它总是可变的,因为不同的函数调用将具有不同数量的参数数据。
Tony R

请注意,处理器在处理堆栈帧时必须知道堆栈帧的大小。发生这种情况时,已经确定了数据的大小。动态语言像静态语言一样被编译为机器代码,但是通常是及时完成的​​,因此编译器可以保持动态性,并且处理器可以使用“已知”帧大小。不要将高级语言与机器代码/汇编相混淆,这是实际发生的事情。
Tony R

好吧,但是动态语言也有它们的调用栈,不是吗?我的意思是说,例如,如果Python要执行某个过程,则有关该过程的数据存储在某些Python解释器的结构内部,对吗?所以我的意思是调用堆栈不仅存在于较低的级别。
ikostia 2012年

阅读了一些维基百科文章后,我得到了纠正(有点)。堆栈帧的大小在编译时可以保持未知。但是当处理器使用堆栈+帧指针时,它必须知道大小是多少。大小可以是可变的,但是处理器知道大小,这就是我想说的。
Tony R

5

程序员可能对栈框架的疑问不是广义的(它是栈中的一个单一实体,仅服务于一个函数调用,并保留返回地址,自变量和局部变量),而是从狭义上讲–当该术语stack frames在编译器选项的上下文。

这个问题的作者是否有这个意思,但是从编译器选项的角度来看,堆栈框架的概念是一个非常重要的问题,此处其他答复均未涉及。

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

  • / Oy(省略了框架指针)

GCC具有以下内容:

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

英特尔C ++编译器具有以下功能:

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

具有以下别名:

  • /好

Delphi具有以下命令行选项:

  • -$ W +(生成堆栈帧)

从特定的角度来看,从编译器的角度来看,栈帧只是例程入口和出口代码,它将锚点压入栈-也可以用于调试和异常处理。调试工具可以扫描堆栈数据,并使用这些定位符进行回溯,同时定位call sites在堆栈中,即,以分层调用它们的顺序显示功能名称。对于英特尔架构,它是push ebp; mov ebp, espenter表示进入,mov esp, ebp; pop ebpleave表示退出。

这就是为什么对于程序员而言,了解编译器选项中的堆栈框架非常重要的原因-因为编译器可以控制是否生成此代码。

在某些情况下,编译器可以省略堆栈帧(例程的进入和退出代码),并且将直接通过堆栈指针(SP / ESP / RSP)而不是便捷的基本指针(BP / ESP / RSP)。省略堆叠框架的条件,例如:

  • 该函数是叶子函数(即不调用其他函数的最终实体);
  • 没有try / finally或try / except或类似的构造,即不使用任何异常;
  • 不会在堆栈上使用传出参数调用例程;
  • 该函数没有参数;
  • 该函数没有内联汇编代码;
  • 等等...

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


-1

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

总是有一个SP,但是在某些ABI(例如ARM和PowerPC)上,FP是可选的。仅可以使用SP抵消需要放置到堆栈中的参数。是否为函数调用生成堆栈帧取决于参数的类型和数量,局部变量以及通常如何访问局部变量。在大多数ISA上,首先使用寄存器,并且如果参数要多于专用于传递参数的寄存器,则这些参数将被放置到堆栈中(例如x86 ABI有6个寄存器用于传递整数参数)。因此,有时某些功能不需要将堆栈框架放在堆栈上,只需将返回地址压入堆栈即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.