似乎我在编程语言设计中得到了调用堆栈的想法。但我找不到(可能,我只是搜索不够努力)任何像样的解释堆栈帧是什么。
所以我想请人用几句话给我解释一下。
“调用堆栈由堆栈帧组成……”——维基百科
堆栈帧是你放在堆栈上的东西。它们是包含要调用的子例程信息的数据结构。
堆栈帧是推入堆栈的数据帧。在调用堆栈的情况下,堆栈帧将表示函数调用及其参数数据。
如果我没记错的话,函数返回地址首先被压入堆栈,然后是局部变量的参数和空间。它们一起构成了“框架”,尽管这可能依赖于体系结构。处理器知道每个帧中有多少字节,并在帧被推入和弹出堆栈时相应地移动堆栈指针。
高级调用栈和处理器调用栈之间有很大的区别。
当我们谈论处理器的调用堆栈时,我们谈论的是在汇编代码或机器代码中处理字节/字水平的地址和值。当谈到高级语言时,有“调用堆栈”,但它们是由运行时环境管理的调试/运行时工具,以便您可以记录程序(在高级上)发生了什么错误。在这个级别上,像行号、方法和类名这样的东西通常是已知的。当处理器得到代码时,它完全没有这些东西的概念。
快速总结一下。也许有人有更好的解释。
调用堆栈由一个或多个堆栈帧组成。每个堆栈帧对应于对函数或过程的调用,它们具有不,但以返回结束。
要使用堆栈框架,线程保留两个指针,一个称为堆栈指针 (SP),另一个称为帧指针 (《外交政策》)。SP总是点 到“顶级”;的 堆栈,而《外交政策》总是点 到“顶级”;的 SP1。此外,线程还维护一个SP2 (SP3),它指向下一条要执行的指令。
以下数据存储在堆栈中:
关于堆栈的清理有不同的调用约定。
如果你理解栈很好,那么你将在程序理解记忆是如何工作的,如果你在程序理解记忆是如何工作的理解函数存储在程序如果你了解函数存储在程序你会理解递归函数是如何工作的,如果你理解递归函数是如何工作的,你会理解编译器是如何工作的,如果你理解编译器是如何工作的将是编译器和调试任何程序很容易
让我来解释一下stack的工作原理:
首先,你必须知道函数是如何在堆栈中表示的:
堆存储动态分配的值
让我们用例子来理解:
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)
现在了解这个程序的部分内容:
现在让我们看看什么是堆栈以及堆栈部分是什么:
堆栈的分配:
记住一件事:如果任何函数的返回条件得到满足,无论它是否加载了局部变量,它都会立即从堆栈中返回它的堆栈帧。这意味着每当任何递归函数得到基本条件的满足,并且我们在基本条件之后返回时,基本条件将不会等待加载位于程序“else”部分的局部变量。它将立即从堆栈中返回当前帧,下一帧现在位于激活记录中。
在实践中可以看到:
块的回收:
所以现在只要函数遇到return语句,它就会从堆栈中删除当前帧。
当从堆栈返回时,值将以与它们在堆栈中分配的原始顺序相反的顺序返回。
堆栈帧是与函数调用相关的打包信息。这些信息通常包括传递给th函数的参数、局部变量以及终止时返回的位置。激活记录是堆栈帧的另一个名称。堆栈框架的布局由制造商在ABI中确定,支持ISA的每个编译器都必须符合这个标准,然而布局方案可以依赖于编译器。通常堆栈帧大小不受限制,但有一个概念称为“红色/保护区”,以允许系统调用…等等来执行而不干扰堆栈帧。
总有一个SP,但在一些abi上(例如ARM和PowerPC) FP是可选的。需要放在堆栈上的参数只能使用SP来抵消。是否为函数调用生成堆栈帧取决于参数的类型和数量、局部变量以及通常如何访问局部变量。在大多数isa上,首先使用寄存器,如果参数比专用于传递参数的寄存器多,则将这些参数放在堆栈中(例如,x86 ABI有6个寄存器用于传递整数参数)。因此,有时,一些函数不需要将堆栈帧放在堆栈中,只需要将返回地址推入堆栈。
程序员对堆栈框架的问题可能不是广义的(它是堆栈中的一个实体,只服务于一个函数调用,并保存返回地址、参数和局部变量),而是狭义的——当术语stack frames在编译器选项的上下文中被提到时。
stack frames
无论问题的作者是否有意为之,但是从编译器选项的角度来看,堆栈框架的概念是一个非常重要的问题,这里的其他回答没有涉及到。
例如,Microsoft Visual Studio 2015 C/ c++编译器具有与stack frames相关的以下选项:
GCC有以下几个方面:
英特尔c++编译器有以下:
它有以下别名:
Delphi有以下命令行选项:
在这种特殊意义上,从编译器的角度来看,堆栈帧就是例程的进入和退出代码,它将一个锚推到堆栈中——也可以用于调试和异常处理。调试工具可以扫描堆栈数据并使用这些锚点进行回溯,同时在堆栈中定位call sites,也就是说,以它们被层次结构调用的顺序显示函数名。对于Intel体系结构,入口是push ebp; mov ebp, esp或enter,出口是mov esp, ebp; pop ebp或leave。
call sites
push ebp; mov ebp, esp
enter
mov esp, ebp; pop ebp
leave
这就是为什么对于程序员来说,当涉及到编译器选项时,理解堆栈框架是非常重要的——因为编译器可以控制是否生成这段代码。
省略堆栈帧(例程的入口和退出代码)可以使代码更小、更快,但它也可能对调试器回溯堆栈中的数据并将其显示给程序员的能力产生负面影响。这些是编译器选项,用于确定函数在何种条件下应该具有入口和退出代码,例如:(a) always, (b) never, (c)当需要时(指定条件)。