简而言之,解释堆栈框架的概念

问题描述 投票:157回答:6

看来我在编程语言设计中得到了调用栈的概念。但我找不到(可能,我只是不够努力搜索)任何堆栈帧的正确解释。

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

callstack
6个回答
160
投票

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

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

EDIT:

高级调用堆栈与处理器调用堆栈之间存在很大差异。

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


49
投票

如果您非常了解堆栈,那么您将了解内存在程序中的工作方式,如果您了解内存在程序中的工作方式,您将了解函数存储在程序中的方式,如果您了解函数存储在程序中的方式,您将理解递归函数的工作原理以及是否你理解递归函数是如何工作的,你会理解编译器是如何工作的,如果你理解编译器如何工作,你的思想将作为编译器工作,你将很容易地调试任何程序

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

首先,你必须知道堆栈中的函数存储:

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

enter image description here

让我们用例子来理解:

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)

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

enter image description here

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

enter image description here

堆栈的分配:

记住一件事,如果任何函数得到“返回”,无论它已经加载了所有的本地变量,或者它将立即从堆栈中返回的任何东西将是他的堆栈帧。这意味着当任何递归函数获得基本条件并且我们在基本条件之后放置返回因此基本条件将不等待加载位于程序的“else”部分中的局部变量它将立即从堆栈返回当前帧,现在如果一个帧返回下一帧是激活记录。在实践中看到这个:

enter image description here

释放块:

所以现在每当函数找到return语句时,它都会从堆栈中删除当前帧。

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

enter image description here


40
投票

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

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

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

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

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


13
投票

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

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


3
投票

程序员可能对堆栈框架的问题不是一个广义的术语(它是堆栈中的一个单独的实体,仅用于一个函数调用并保留返回地址,参数和局部变量),但从狭义上说 - 当提到术语stack frames时在编译器选项的上下文中。

问题的作者是否有意,但编译器选项方面的堆栈框架的概念是一个非常重要的问题,这里没有其他答复所涵盖。

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

  • / Oy(帧指针省略)

GCC有以下内容:

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

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

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

其中包含以下别名:

  • / Oy公司

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个寄存器来传递整数参数)。因此,有时,某些函数不需要将堆栈帧放置在堆栈上,只需将返回地址压入堆栈。

© www.soinside.com 2019 - 2024. All rights reserved.