【问题标题】:Stack size on Linux - Limited stack size vs automatic stack expansionLinux 上的堆栈大小 - 有限堆栈大小与自动堆栈扩展
【发布时间】:2023-04-10 21:25:01
【问题描述】:
  1. 我记得在我十几岁的时候第一次从我的书中学习记忆处理,下面的例子:
void foo()
{
    int a[1000000]; // waste of stack
    // ...
}

void foo()
{
    int* ptr = malloc(1000000 * sizeof(int)); // much better, because the size of stack is limited
    // ...
}

(是的,我听说有时编译器可能会将第二个代码优化为第一个)

我一直把堆栈想象成一个“固定的、有限的”内存空间,我的旧书说“堆栈非常小,不要浪费它”,但是堆可以根据需要利用 RAM(限制来自明显的硬件) .

但是:

https://unix.stackexchange.com/questions/63742/what-is-automatic-stack-expansion

Dynamic expansion of the Linux stack

https://www.youtube.com/watch?v=7aONIVSXiJ847:05

那么如果我理解的很好,栈的大小不是我们选择堆的主要原因吗? 好吧,我们仍然需要malloc()/calloc(),因为我们不知道数组大小的编译时间(并且想要避免 VLA)。但是除了这个推理之外,还有什么其他原因将我们的数据实例存储在堆上吗?

我一直认为堆会随着我们malloc() 的增加而扩展。这是正确的吗?

  1. 我很困惑如果堆栈是动态增长的,那么为什么会发生堆栈溢出?

这是关于虚拟内存中的 Linux 程序的图片:

https://cdn-images-1.medium.com/max/1200/1*8b9-Z3FV6X9SP9We8gSC3Q.jpeg

两个箭头之间的虚拟内存空间耗尽时会发生堆栈溢出?

  1. youtube 视频 (https://www.youtube.com/watch?v=7aONIVSXiJ8) 在 47:40 说堆栈扩展可能会导致新分配的页面不会与堆栈的其余部分在物理上连续。新分配的页面将被映射到进程的堆栈中,因此程序的虚拟内存布局保持平坦。我说的对吗?

  2. 堆向上增长(例如当malloc() 时)。但是,这再次得出结论,堆的大小是有限的(在堆栈之上,在 BSS 底部)。如何通过堆利用整个 RAM?如果低地址为 0,而高地址是 RAM 可用的最高内存地址,我可以对此进行成像的唯一原因。一个进程可能“看到整个 RAM 都可用指针”。这些是虚拟内存地址,因此 CPU 将它们转换为物理地址。我理解的好吗?

  3. 随着我们分配更多堆栈和堆动态增长,我们不必关心使用brk() 手动增加程序中断或使用alloca() 函数在堆栈上分配。这些是内核调用的低级函数,我们不直接调用这些函数。这是正确的吗?

如果我理解正确,如果你能回答,我将不胜感激。

【问题讨论】:

    标签: linux memory kernel heap-memory stack-memory


    【解决方案1】:

    堆栈可以增长,但不是无限期的。如您链接到的第一个问题中的图表所示,堆栈和堆都可以增长到空闲内存区域,但如果它们最终继续增长,它们就会相互碰撞。

    编写需要大量堆栈增长的程序并不常见。通常,如果程序不断增长堆栈,则表明导致无限递归的错误。限制堆栈大小会捕获这些错误。虽然有一些深度递归的算法,但它们并不常见,通常可以重构为迭代算法。

    问题 int a[1000000]; 不是说它“浪费”了堆栈。大多数架构对单个堆栈帧的大小都有相对较小的限制,因此您不能将这么大的数组用作局部变量。

    但除此之外,选择堆内存还是栈内存的通常原因与数据的使用方式有关。堆栈上的变量在程序代码中静态声明。如果您需要可变数量的对象,通常需要使用堆(C 有可变长度数组,但 C++ 没有,您不能像使用 realloc() 那样调整它们的大小)。此外,当函数返回时,分配在堆栈上的内存会消失,因此您必须使用堆对象来存储在单个函数之外持续存在的数据。

    别担心他在视频中 47:40 说的内容。应用程序只处理虚拟内存,物理内存是完全隐藏的,只与内核中的虚拟内存子系统内部相关。

    进程中断由运行时库的malloc() 实现使用。您通常不会直接在应用程序中处理它。

    【讨论】:

    • 感谢您的详细回答。正如我所调查的那样,堆栈溢出是堆栈超出固定堆栈限制时引发的一般错误类型。所以“自动堆栈扩展”意味着堆栈可以增长运行时,但有一个固定的限制(例如由编译器或执行环境确定)。这是正确的吗?
    • 是的。使用ulimit -s 查看配置的堆栈大小限制。
    • 一切最终都受到进程中可用虚拟内存量的限制。一些堆管理器将使用mmap() 来比仅使用brk() 更有效地使用内存。
    • 忘掉内存吧。作为应用程序程序员,您从不处理 RAM,只处理虚拟内存。
    • ulimit -l锁定内存,而不是虚拟内存。虚拟内存限制为ulimit -v
    猜你喜欢
    • 1970-01-01
    • 2023-03-14
    • 2012-06-26
    • 2014-09-24
    • 1970-01-01
    • 2013-11-04
    • 2010-11-24
    • 2021-03-26
    • 1970-01-01
    相关资源
    最近更新 更多