调用堆栈和堆栈有什么区别?

what's the difference between callstack and stack?

我想我可能问了一个非常错误的问题,但我真的试图通过谷歌搜索来理解它,但没有运气。

众所周知,我们有栈和堆。动态分配的堆,局部变量的堆和 e.t.c.

假设我有以下 C++ 代码。

void bla(int v1, int v2, int v3) {
    int g = v1 + v2+ v3;
}

void nice(int g){
    int z = 20;
    int k = 30;
    bla(g, z, k);
}

int main(){
    cout<<"Hello World";
    nice(40);
}

现在,让我们假设有一个堆栈。我知道例如值 z,k,g 将存储在堆栈中。但是当我调用调用 bla 的函数 nice 时,这些函数存储在哪里?我读到每个函数执行都会导致调用堆栈大小增加 1。我会说即使创建局部变量也会导致调用堆栈增加 1。

那么,这些(callstackstack)到底有什么关系?

这是我的假设:

当我们调用 nice 时,会创建全新的 stack。在那里,我们存储 z and k。当 nice 调用 bla 时,现在会为 bla 创建另一个 stack 并且第二个堆栈存储 v1,v2,v3,g。等等。每个函数都需要自己的 callstack,但我们也可以调用它 stack

每个 运行 进程都分配了一块内存,它称之为“堆栈”。并且,此内存区域用于 both 来表示“call/return 序列”(通过所谓的“堆栈帧”), 被调用的每个例程使用的所谓“局部变量”。他们是一回事。

通常,不同的CPU寄存器用于同时指向每个事物。一个寄存器指向“局部变量”值,而另一个完全不同的寄存器指向“堆栈帧”。 (在解释器中,使用了类似的机制。)还有第三个寄存器指示当前“堆栈顶部”。

在开始执行每个新函数时,“栈顶”简单地向前移动以说明局部变量,在“局部变量指针”记住它曾经是什么之后。

当“return from subroutine”发生时,“栈顶指针”恢复到某个先前的值,并且所有曾经存在于“它上面”的东西现在,从字面上看,忘记了。因为已经不重要了。

So, how are those(callstack, stack) related at all ?

它们之间的关系非常密切。他们是一样的东西。也叫执行栈。

不要将此“调用堆栈”与“堆栈”数据结构的一般概念混淆。调用堆栈称为 a 堆栈,因为它描述了调用堆栈的结构。

causes call stack size to increase by 1

肯定是“1”,但增加的单位是什么?当您调用一个函数时,堆栈指针会增加一个堆栈帧。堆栈帧的大小(以字节为单位)各不相同。函数的框架足够大,可以包含所有局部变量(参数也可以存储在堆栈中)。

因此,如果您希望以字节为单位测量增量,那么它不是 1,而是大于或等于 0 的某个数字。

I'd say that even creating local variables also causes call stack to be increased by 1.

正如我所描述的,局部变量会影响调用函数时堆栈指针的递增方式。

When we call nice, completely new stack gets created.

不是,整个执行线程中的所有函数调用共享同一个堆栈。


其中大部分 none 是由 C++ 语言指定的,而是在典型情况下适用于大多数 C++ 实现的实现细节,但为了便于理解而进行了简化。

Stack表示一种数据结构,由一组通常相似的项目组成,具有以下能力:

  • push: 添加一个项目到堆栈的“顶部”,它将成为新的顶部
  • pop:将item从顶部移除,这样原来在顶部的item又会回到顶部;此操作 returns 您删除的项目
  • top: 获取堆栈的顶部项目,而不修改您的堆栈

您的内存有一个称为“堆栈”的部分,正如您正确理解的那样,其中存储了函数。那就是调用栈。然而,堆栈和调用堆栈并不是 100% 等价的,因为在许多其他情况下都会使用堆栈,基本上只要您需要 LIFO(后进先出)处理。例如,它用于 CPU 的 LIFO 策略,或者当您在图形中进行深度优先搜索时。总之,栈是一种数据结构,应用场景很多,内存中的调用栈就是一个突出的例子。

所以,为什么使用堆栈来存储内存中的函数调用。让我们考虑嵌入式函数调用,例如:

f1(f2(...(fn(x))...))

根据经验,为了评估 fi,1 <= i < n,您需要评估 fj,其中 1 < j <= n,假设 i < j。结果,f1 最先被调用,但最后被求值。因此,您有一个 LIFO 处理,最好通过使用堆栈来完成。