每个函数在 c 中都有自己的堆栈吗?

Does every function get its own stack in c?

我最近学习了堆栈,所以我正在尝试看看堆栈大小是多少以及溢出时会发生什么。我发现在 Unix 上,默认堆栈大小为 8 MiB,这支持了我的发现,因为我无法在 main 函数中声明大小大于或等于 8 MiB 的字符串。但是,当我在 main() 中声明一个变量时,它会影响其他函数。例如:

#include <stdio.h>

void foo(void)
{
    long int size = 1024*1024*2;
    char str[size];
    str[size - 1] = 'a';
    printf("%c\n", str[size - 1]);
}

int main(int argc, char** argv)
{
    long int size = 1024*1024*6;
    char str[size];
    str[size - 1] = 'a';
    printf("%c\n", str[size - 1]);
    foo();
    return 0;
}

此代码会导致分段错误,但如果我在 main() 中将字符串大小设置为 5 MiB,则不会出现分段错误。这是否意味着我的 C 程序不能为局部变量(所有函数)分配超过 8 MiB 的 RAM?如果是这样,堆栈的意义是什么?

不,每个函数都没有自己独立的堆栈space。您的程序中只有一个堆栈,您可以使用 有限数量的堆栈 space

堆栈的工作原理

This LIFO behavior is exactly what a function does when returning to the function that called it.

堆栈中的流

  1. 调用者将return地址压入堆栈
  2. 当被调用函数完成执行时,弹出 return 调用堆栈的地址(这个弹出的元素也被称为 堆栈帧)并将控制转移到该地址。
  3. 如果被调用的函数调用另一个函数,它将推送 另一个 return 地址到 相同调用堆栈 的顶部,并且 依此类推,以信息堆叠和出栈为 程序规定。

以上所有过程都发生在同一个栈内存中。每个函数在堆栈中都有自己的 space,但每个函数都在同一个堆栈中分配了 space。这称为程序的 全局调用堆栈 。 它用于存储函数内部使用的局部变量。

然而,动态分配的space存储在堆上。 堆用于存储动态变量。它是进程内存的一个区域。 malloc()calloc()resize()这些内置函数一般都是用来存储动态变量的。

至于堆栈溢出问题,调用堆栈大小受到限制。只能使用一定数量的内存。如果发生许多函数调用,堆栈 space 最终会 运行 出来,这会给你一个 堆栈溢出错误 ,这很可能会导致你的 程序崩溃.

如果你的函数中有很多变量或者你的程序中一些变量需要大量space,那么堆栈space最终会运行出来并且它会导致 堆栈溢出 。例如。在大多数情况下,以下可能会导致堆栈溢出并导致您的程序崩溃:

int main() {
   int A[100000][100000];
}

希望这能消除您的疑虑!

注意:

在多线程环境中,每个线程分别获得自己的调用堆栈space,而不是拥有相同的全局调用堆栈。因此,在多线程环境中,您问题的答案将是 YES.

"stack" 是一个,在内存中共享 space,正如它的名字一样,每个嵌套函数调用 "pushes" 一个新的 "frame"(一组 space 对于局部变量)到共享堆栈上。是的,内存中堆栈 space 的总大小在(当前)正在执行的所有函数之间共享,如果在 运行 期间使用的总大小 space 程序超过了OS 已经预留了它,你会导致(咳咳)"stack overflow" 崩溃。

是为每个函数的调用提供工作space。通常,堆栈 上的任何特定函数 使用的 space 数量非常小——可能是一些整数或几个较小的数组等。想想几十或几百个字节,通常不是千字节,也不是兆字节。这主要是惯用的,当您使用了足够多的自己和他人的代码时,您会习惯于在堆栈中拥有什么是有意义的,而什么是没有意义的。在生产代码中将兆字节大的东西作为实际的局部变量是非常不寻常的。

实际上,现实世界中堆栈溢出错误的主要原因是意外的无限递归——当您最终在没有递归基本情况的情况下一遍又一遍地调用相同的函数时。这些堆栈帧可能每个都很小,但如果调用链是无限的,最终你会溢出。

当你想使用实际的大块内存、大字符串缓冲区等时,你通常会从称为 "the heap" 的不同共享内存块中分配它们。您可以分配(使用 malloc 及其表兄弟)您需要的东西,然后 free 完成后分配。堆的内存 space 对您的程序来说是全局的,不受特定函数调用的约束或相关。

C 语言标准对堆栈一无所知。函数如何调用、参数如何传递以及自动存储对象存储在何处取决于实现。

大多数实现实际上只有一个堆栈,但我会给你一些非常常见的例外情况。

  1. 实时操作系统。许多 RTOS-es 将任务实现为正常功能。作为单独任务的函数将具有单独的堆栈。
  2. 许多多任务库(如 pthread)将为线程(函数)提供单独的堆栈。
  3. 许多硬件设计有不止一个堆栈——例如非常流行的 ARM Cortex uCs——有两个独立的硬件堆栈。

等等等等

Does that mean my c program cannot allocate more than 8MB of ram for local variables (of all functions) ?

是也不是。是的,您的程序不能使用比可用堆栈 space 更多的 space 作为局部变量,无论如何。但是不,您不限于所有函数的 8MB,您只限于当前正在执行的函数的总堆栈 space。一个程序可能包含数千个函数,但在任何给定时刻只会调用其中的一小部分。

当您的程序调用函数时,space 在堆栈上保留用于函数的 return 值及其局部变量。如果该函数调用另一个函数,则 space 将被保留用于下一个函数的 return 值和局部变量。当每个函数 returns 时,读取 return 值并将局部变量和 return 值从堆栈中弹出。因此函数 在执行时使用堆栈 space。

If so, what's the point of stacks ?

重点是提供局部变量所需的 space,以便于 return 将值传递给调用者,并使分配 space 快速高效。函数通常不需要大量存储局部变量,因此 8MB 通常绰绰有余。

如果您发现需要分配大量内存,可以使用内存分配函数轻松实现。假设您需要像您的示例一样创建一个数兆字节的字符串。您通常会使用 malloc()calloc() 之类的函数在堆上而不是堆栈上创建该对象,并且您唯一需要的局部变量是指向已分配内存的指针。