堆栈内存的大小

60

可能是重复问题:
什么是堆和栈?它们在哪里?

关于 c 程序中的内存布局基本概念,我了解到:

  • 该语言使用两个主要的数据结构栈(stack)堆(heap)
  • 栈被创建用于存储子例程的本地变量和书记数据。
  • 堆被创建用于存储程序动态分配的变量。
  • 堆的长度是可变的。(对于栈不太确定)
  • 通常,在执行之前,编译器/语言请求操作系统创建这些数据结构的责任。

问题

  • 栈/堆的初始大小是多少?由谁决定?
  • 它们在物理内存中的位置是什么?我看到一个概括性描述:"栈被创建在最高地址,堆在最低地址"请详细说明。
4个回答

70
“栈是在高地址创建的,堆是在低地址创建的。” 请解释一下。
这是一个误解。它可能有历史真相的基础。有时候也可能与实际情况共鸣。但它不是字面上的真实。
虽然很容易去了解这个问题:
#include <stdlib.h>
#include <stdio.h>

void check(int depth) {
    char c;
    char *ptr = malloc(1);
    printf("stack at %p, heap at %p\n", &c, ptr);
    if (depth <= 0) return;
    check(depth-1);
}

int main() {
    check(10);
    return 0;
}

在我的机器上,我看到:

stack at 0x22ac3b, heap at 0x20010240
stack at 0x22ac0b, heap at 0x200485b0
stack at 0x22abdb, heap at 0x200485c0
stack at 0x22abab, heap at 0x200485d0
stack at 0x22ab7b, heap at 0x200485e0
stack at 0x22ab4b, heap at 0x200485f0
stack at 0x22ab1b, heap at 0x20048600
stack at 0x22aaeb, heap at 0x20048610
stack at 0x22aabb, heap at 0x20048620
stack at 0x22aa8b, heap at 0x20048630
stack at 0x22aa5b, heap at 0x20048640

因此,堆栈向下增长,堆向上增长(正如您可能根据传说所预期的那样),但是堆栈具有较小的地址,并且它们不会相互靠近增长(这个传说已经被打破了)。

顺便说一句,我的check函数是尾递归的,在某些实现中,使用某些编译器选项,您可能看不到堆栈移动。这告诉您关于为什么标准没有强制规定所有这些工作方式的原因--如果它这样做,可能会无意中禁止有用的优化。


1
尾递归在递归调用之前消除当前的堆栈帧,因为在递归调用结束后它不再需要。堆栈仍然向下增长,在这种情况下只是发生了这种情况,没有必要增加堆栈。 - user1706991
6
这不是传说。在现代的非ASLR Linux上,你的程序输出为stack at 0x7fff356d5fd7, heap at 0x1d39010。运行sudo bash -c 'for x in /proc/*/maps; do echo $x; egrep stack\|heap $x; done',可以看到所有进程都类似的输出。2014年曾经利用堆栈和堆重叠的方式进行攻击:“所有这些命令行参数的影响是膨胀堆栈(向下增长)和堆(向上增长),直到它们相互碰撞。” - bain
9
即使存在一个系统,使该陈述为真,但当它被呈现为“一般描述”时,仍然可能是一个神话。这是一种方法,但声称它是“惯用方式”的总体主张是错误的。 - Steve Jessop

22

如前所述,大小是特定于操作系统的。例如,在使用Visual Studio的Windows上,默认堆栈大小为1MB。

msdn

在Linux上,以下命令可以显示您当前的堆栈大小。

ulimit -s or -a
在我的Linux Mint 64位系统上,它显示为8192 KB。 每个程序在加载到内存时都有几个段。在汇编语言中,可以使用.data,.code等前缀(intelx86)来指示它们中的每一个。 数据段有多个子段,堆栈和堆都是其一部分,还有其他几个子段。 堆栈也可以隐式增长,即当您进行另一个函数调用时,会将激活记录推入堆栈,从而利用堆栈的更多内存。这就是为什么无限递归导致程序用完分配的堆栈时崩溃的原因。 当函数调用返回时,该激活记录被弹出,堆栈会缩小。 相反,堆从相反方向增长,并包含所有动态分配的内存。 这两个段增长方向相反的原因是为了最大化它们的组合内存利用率。请注意,正如评论中提到的,这不是C标准,但大多数常见操作系统都已实现此功能。 ------堆栈开始 ----------- 堆栈向下增长 --------除非它们彼此交叉,否则程序可以正常运行。 -------堆开始 ------------堆向上增长 如果程序不使用堆,则堆栈可以利用最大的内存,包括堆的内存。如果程序进行少量递归调用并使用最少的本地变量(即使用较少的堆栈内存),则它可以最大程度地利用堆。 数据段的其他部分是BSS等,可能包含未初始化的静态变量字段。

1
C标准没有规定堆栈扩展的方向。它们都可以向上或向下增长,这在标准中是允许的。 - Alexey Frunze
1
同意。这是一个常见的实现,例如在Linux中。但是像提到的那样,并不是C标准。 - fkl
2
谢谢您的回答!此外,我想引用以下内容:“ulimit”确实显示堆栈大小的上限(而不是编译器使用的上限)。我认为在达到此限制后,程序将以堆栈溢出错误终止。 - Vivek Maran

11

堆栈/堆的初始大小是多少?由谁决定?

这取决于编译器和操作系统。

它们在物理内存的哪里创建?一般描述是"堆创建在顶端地址,栈创建在低端地址"。

这也取决于编译器和操作系统。

实际上,语言标准没有规定最小堆栈大小,也没有明确指定堆栈或堆在内存中的位置。原因是为了使C程序不那么依赖于这些细节,从而使其在不同平台(即不同操作系统、不同CPU和不同编译器)上更具可移植性。


6
首先,C标准对平台实现堆栈/堆的方式没有任何规定。
堆栈/堆初始化创建时的初始大小是多少?由谁决定?
通常,操作系统为每个进程分配固定大小的堆栈(具体取决于平台)。堆的大小没有限制,程序通常拥有所有可用的虚拟地址空间。
它们在物理内存中的位置在哪里创建?
这是平台特定的。通常,堆栈向下增长,堆向上增长。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接