ChatGPT解决这个技术问题 Extra ChatGPT

简述栈帧的概念

似乎我在编程语言设计中得到了调用堆栈的想法。但是我找不到(可能是我搜索得不够努力)关于什么是堆栈帧的任何体面的解释。

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


T
Thomas

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

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

编辑:

更高级别的调用栈和处理器的调用栈有很大的不同。

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


“处理器知道每帧中有多少字节,并在帧被推入堆栈和从堆栈中弹出时相应地移动堆栈指针。” - 我怀疑处理器对堆栈一无所知,因为我们通过 subbing(分配)、push 和 pop 来操作它。所以这里有一些调用约定来解释我们应该如何使用堆栈。
处理器具有堆栈帧指针,即包含函数帧地址的寄存器。
v
virmis_007

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

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

首先,您必须知道函数在堆栈中的表示方式:

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

https://i.stack.imgur.com/P56ru.jpg

让我们通过示例来理解:

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)

现在了解该程序的部分内容:

https://i.stack.imgur.com/Mu9nS.png

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

https://i.stack.imgur.com/1PI03.png

堆栈分配:

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

在实践中看到这一点:

https://i.stack.imgur.com/N94IW.png

块的释放:

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

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

https://i.stack.imgur.com/5TrkZ.png


堆栈向下增长,堆向上增长,您在图表中将它们颠倒过来。 CORRECT DIAGRAM HERE
@Rafael 抱歉,我说的是增长方向,我不是在说堆栈增长的方向。生长方向和堆栈生长方向是有区别的。看这里stackoverflow.com/questions/1677415/…
拉斐尔是对的。还有第一张图是错的。用其他东西替换它(在谷歌图片中搜索“堆堆栈”)。
因此,如果我理解正确,在您的第三张图中,有 3 个堆栈帧,因为 hello() 递归调用了 hello(),然后(再次)递归调用了 hello(),并且全局框架是调用第一个 { 1}?
链接将我们带到哪里?出于安全考虑,应尽快删除这些链接。
U
Utmost Creator

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

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

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

以下内容存储在堆栈中:

局部变量和临时变量;

当前指令的实际参数(过程、函数等)。

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


不要忘记子程序的返回地址在堆栈上。
帧指针也是 x86 术语中的基指针
我想强调一个帧指针指向当前活动过程化身的堆栈帧的开头。
W
Waleed Khan

“调用堆栈由堆栈帧组成……” — Wikipedia

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


抱歉,我不知道我是如何在 wiki 上错过这个的。谢谢。我是否理解正确,在动态语言中,框架的大小不是一个常数值,因为函数的局部变量并不完全已知?
框架的大小和性质在很大程度上取决于机器的架构。事实上,调用堆栈的范式是特定于体系结构的。据我所知,它总是可变的,因为不同的函数调用会有不同数量的参数数据。
请注意,在处理堆栈帧时,处理器必须知道堆栈帧的大小。发生这种情况时,数据的大小已经确定。动态语言像静态语言一样被编译为机器代码,但通常是即时完成的,这样编译器可以保持动态,处理器可以使用“已知”的帧大小。不要将高级语言与机器代码/汇编混淆,这是实际发生的地方。
好吧,但是动态语言也有它们的调用堆栈,不是吗?我的意思是,如果说,Python 想要执行某个过程,关于这个过程的数据存储在某个 Python 解释器的结构中,我说的对吗?所以我的意思是调用堆栈不仅存在于低级别。
在阅读了一些维基百科文章后,我得到了纠正(有点)。堆栈帧的大小在编译时可能保持未知。但是当处理器使用堆栈+帧指针时,它必须知道大小是多少。大小可以是可变的,但处理器知道大小,这就是我想说的。
M
Maxim Masiutin

程序员可能对堆栈帧有疑问,不是广义的(它是堆栈中的一个单一实体,只服务一个函数调用并保留返回地址、参数和局部变量),而是狭义的——当术语 stack frames在编译器选项的上下文中提到。

无论问题的作者是否有意,但从编译器选项方面来看堆栈框架的概念是一个非常重要的问题,此处其他回复未涵盖。

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

/Oy(帧指针省略)

海合会有以下内容:

-fomit-frame-pointer(不要将帧指针保存在不需要的函数的寄存器中。这避免了保存、设置和恢复帧指针的指令;它还在许多函数中提供了一个额外的寄存器)

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

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

它具有以下别名:

/Oy

Delphi 有以下命令行选项:

-$W+(生成堆栈帧)

在这个特定的意义上,从编译器的角度来看,堆栈帧只是例程的进入和退出代码,它将一个锚点推送到堆栈——也可以用于调试和异常处理.调试工具可以扫描堆栈数据并使用这些锚点进行回溯,同时在堆栈中定位call sites,即按照函数被分层调用的顺序显示函数的名称。对于 Intel 架构,push ebp; mov ebp, espenter 表示进入,mov esp, ebp; pop ebpleave 表示退出。

这就是为什么当涉及到编译器选项时,了解堆栈帧在什么位置对程序员来说非常重要——因为编译器可以控制是否生成此代码。

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

该函数是叶函数(即不调用其他函数的终端实体);

没有 try/finally 或 try/except 或类似的结构,即没有使用异常;

没有使用堆栈上的传出参数调用例程;

该函数没有参数;

该函数没有内联汇编代码;

ETC...

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


d
drunk teapot

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

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅