我似乎明白了编程语言设计中调用栈的概念。但是我找不到(可能是因为我搜索得不够努力)任何关于堆栈框架是什么的像样的解释。

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


当前回答

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

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

编辑:

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

When we talk about a processor's call stack, we are talking about working with addresses and values at the byte/word level in assembly or machine code. There are "call stacks" when talking about higher-level languages, but they are a debugging/runtime tool managed by the runtime environment so that you can log what went wrong with your program (at a high level). At this level, things like line numbers and method and class names are often known. By the time the processor gets the code, it has absolutely no concept of these things.

其他回答

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

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

编辑:

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

When we talk about a processor's call stack, we are talking about working with addresses and values at the byte/word level in assembly or machine code. There are "call stacks" when talking about higher-level languages, but they are a debugging/runtime tool managed by the runtime environment so that you can log what went wrong with your program (at a high level). At this level, things like line numbers and method and class names are often known. By the time the processor gets the code, it has absolutely no concept of these things.

如果你理解栈很好,那么你将在程序理解记忆是如何工作的,如果你在程序理解记忆是如何工作的理解函数存储在程序如果你了解函数存储在程序你会理解递归函数是如何工作的,如果你理解递归函数是如何工作的,你会理解编译器是如何工作的,如果你理解编译器是如何工作的将是编译器和调试任何程序很容易

让我来解释一下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)

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

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

堆栈的分配:

Remember one thing: if any function's return condition gets satisfied, no matter it has loaded the local variables or not, it will immediately return from stack with it's stack frame. It means that whenever any recursive function get base condition satisfied and we put a return after base condition, the base condition will not wait to load local variables which are located in the “else” part of program. It will immediately return the current frame from the stack following which the next frame is now in the activation record.

在实践中可以看到:

块的回收:

所以现在只要函数遇到return语句,它就会从堆栈中删除当前帧。

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

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

无论问题的作者是否有意为之,但是从编译器选项的角度来看,堆栈框架的概念是一个非常重要的问题,这里的其他回答没有涉及到。

例如,Microsoft Visual Studio 2015 C/ c++编译器有以下与堆栈框架相关的选项:

/Oy(帧指针遗漏)

GCC有以下几个方面:

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

英特尔c++编译器有以下:

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

它有以下别名:

/啧啧

Delphi有以下命令行选项:

$W+(生成堆栈帧)

In that specific sense, from the compiler’s perspective, a stack frame is just the entry and exit code for the routine, that pushes an anchor to the stack – that can also be used for debugging and for exception handling. Debugging tools may scan the stack data and use these anchors for backtracing, while locating call sites in the stack, i.e. to display names of the functions in the order they have been called hierarchically. For Intel architecture, it is push ebp; mov ebp, esp or enter for entry and mov esp, ebp; pop ebp or leave for exit.

这就是为什么对于程序员来说,当涉及到编译器选项时,理解堆栈框架是非常重要的——因为编译器可以控制是否生成这段代码。

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

该函数是叶函数(即不调用其他函数的终端实体); 没有try/finally或try/except或类似的结构,即没有使用异常; 没有例程使用堆栈上的传出参数被调用; 函数没有参数; 函数没有内联汇编代码; 等等……

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

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

调用堆栈由一个或多个堆栈帧组成。每个堆栈帧对应于对一个函数或过程的调用,该函数或过程尚未以返回结束。

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

以下数据存储在堆栈中:

局部变量和临时变量; 当前指令的实际参数(过程、函数、 等等)。

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

Stack frame is the packed information related to a function call. This information generally includes arguments passed to th function, local variables and where to return upon terminating. Activation record is another name for a stack frame. The layout of the stack frame is determined in the ABI by the manufacturer and every compiler supporting the ISA must conform to this standard, however layout scheme can be compiler dependent. Generally stack frame size is not limited but there is a concept called "red/protected zone" to allow system calls...etc to execute without interfering with a stack frame.

There is always a SP but on some ABIs (ARM's and PowerPC's for example) FP is optional. Arguments that needed to be placed onto the stack can be offsetted using the SP only. Whether a stack frame is generated for a function call or not depends on the type and number of arguments, local variables and how local variables are accessed generally. On most ISAs, first, registers are used and if there are more arguments than registers dedicated to pass arguments these are placed onto the stack (For example x86 ABI has 6 registers to pass integer arguments). Hence, sometimes, some functions do not need a stack frame to be placed on the stack, just the return address is pushed onto the stack.