- 栈和堆是什么?
- 它们在计算机内存中的物理位置在哪里?
- 它们在多大程度上受操作系统或语言运行时的控制?
- 它们的作用范围是什么?
- 是什么决定它们的大小?
- 是什么使它们更快?
堆栈是一段内存,可以通过几个关键的汇编语言指令进行操作,例如'pop'(从栈中删除并返回值)和'push'(将值推到栈上),还有call(调用子程序-这会将返回地址压入栈上)和return(从子程序返回-这会弹出地址并跳转到它)。它是位于栈指针寄存器下方的内存区域,可以根据需要设置。堆栈也用于向子例程传递参数,并在调用子例程之前保留寄存器中的值。
堆是操作系统通过类似于malloc的系统调用提供给应用程序的一部分内存。在现代操作系统中,这段内存是一组只有调用进程才能访问的页面。
堆栈的大小在运行时确定,在程序启动后通常不会增长。在C程序中,堆栈的大小需要足够大,以容纳每个函数内声明的变量。堆将根据需要动态增长,但是操作系统最终决定(通常会比malloc请求的值多增加堆,以便将来的某些malloc不需要返回内核获取更多内存。此行为通常是可自定义的)
由于在启动程序之前已经分配了堆栈,因此您永远不需要在使用堆栈之前malloc,因此这是一个小优势。在实践中,很难预测现代操作系统中虚拟内存子系统中什么会快,什么会慢,因为页面的实现方式以及它们存储的位置是实现细节。
我认为许多其他人已经就这个问题给出了大多数正确的答案。
然而,一个被忽略的细节是,“堆(heap)”实际上可能应该被称为“自由存储(free store)”。这种区别的原因是最初的free store是用一种名为“二项堆(binomial heap)”的数据结构实现的。因此,从早期的malloc()/free()分配是从堆中分配的。但是,在今天,大多数自由存储都是使用非常复杂的数据结构实现的,而不是二项堆。
C
语言需要使用“堆栈”。虽然这是实现 C99 6.2.4 自动存储期对象
(变量)的(远远)主要范例,但这是一个常见的误解。实际上,“堆栈”这个词甚至没有出现在 C99
语言标准中:http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf - johnealloca
有多可移植?例如,它在Windows上工作吗?它只适用于类Unix操作系统吗? - Peter Mortensen简单来说,栈是本地变量创建的地方。每次调用子例程时,程序计数器(指向下一条机器指令的指针)和任何重要寄存器(有时还包括参数)都会被推入栈中。然后,在子例程内部的任何本地变量都会被推入栈中(并从那里使用)。当子例程完成时,所有这些东西都会从栈中弹出。PC和寄存器数据在弹出时会被放回原处,所以你的程序可以继续运行。
堆是动态内存分配的区域(采用显式的“new”或“allocate”调用)。它是一种特殊的数据结构,可以跟踪各种大小的内存块及其分配状态。
在“经典”系统中,RAM的布局是栈指针从内存底部开始,堆指针从顶部开始,并向彼此增长。如果它们重叠,则会耗尽RAM。但是,这在现代多线程操作系统中不起作用。每个线程都必须有自己的栈,而这些栈可以动态创建。
来自WikiAnwser。
当一个函数或方法调用另一个函数,而后者又调用另一个函数,以此类推,所有这些函数的执行都保持挂起状态,直到最后一步函数返回其值。
这些挂起的函数调用链就是栈,因为栈中的元素(函数调用)彼此依赖。
在异常处理和线程执行中考虑栈是很重要的。
堆只是程序用于存储变量的内存。 堆的元素(变量)彼此没有依赖关系,并且可以随时随机访问。
Stack 栈
Heap 堆
栈用于静态内存分配,堆用于动态内存分配,两者都存储在计算机的RAM中。
栈
栈是一种“后进先出”(Last In, First Out,LIFO)的数据结构,由CPU密切管理和优化。每次函数声明一个新变量时,它会被“推入”栈中。然后每次函数退出时,该函数推送到栈上的所有变量都将被释放(即删除)。一旦栈变量被释放,该内存区域就可以为其他栈变量提供空间。
使用栈存储变量的优点是,内存由系统自动管理。您不必手动分配内存,也不必在不再需要时释放内存。此外,由于CPU有效地组织了栈内存,因此从栈变量读取和写入非常快速。
更多信息请查看这里。
堆
堆是计算机内存的一个区域,不会被自动管理,也不像CPU那样受到严格的管理。它是一个更自由浮动的内存区域(并且更大)。要在堆上分配内存,必须使用内置的C函数malloc()或calloc()。一旦你在堆上分配了内存,就需要使用free()来释放那些不再需要的内存。
如果你未能这样做,你的程序将出现所谓的内存泄漏。也就是说,堆上的内存仍然被保留(并且不可用于其他进程)。正如我们将在调试部分中看到的那样,有一种名为Valgrind的工具可以帮助你检测内存泄漏。
与栈不同,堆对变量大小没有限制(除了计算机的显然物理限制)。访问堆内存稍微慢一些,因为必须使用指针来访问堆内存。我们很快就会讨论指针。
与栈不同,在堆上创建的变量可被程序中任何函数访问。堆变量本质上具有全局作用域。
可以在这里找到更多信息。
栈上分配的变量直接存储在内存中,访问该内存非常快,其分配在程序编译时处理。当一个函数或方法调用另一个函数,后者再调用另一个函数等等时,所有这些函数的执行都保持暂停状态,直到最后一个函数返回其值。栈始终按LIFO顺序保留,最近保留的块始终是下一个要释放的块。这使得跟踪堆栈非常简单,从堆栈中释放块只需要调整一个指针。
在堆上分配的变量在运行时分配其内存,访问该内存有点慢,但堆的大小仅受虚拟内存大小的限制。堆的元素彼此没有依赖关系,可以随时随意地随机访问。您可以随时分配块并随时释放它。这使得跟踪堆的哪些部分在任何给定时间分配或空闲变得更加复杂。
如果你在编译时知道需要分配多少数据,并且数据量不太大,那么可以使用栈。如果你不确定在运行时需要分配多少数据,或者需要分配大量数据,则可以使用堆。
在多线程情况下,每个线程都有自己完全独立的栈,但它们会共享堆。栈是线程特定的,而堆是应用程序特定的。在异常处理和线程执行中考虑栈非常重要。
每个线程都有一个栈,而通常只有一个堆供应用程序使用(尽管为不同类型的分配使用多个堆并不罕见)。
在运行时,如果应用程序需要更多的堆空间,它可以从空闲内存中分配内存;如果栈需要内存,它也可以从为应用程序分配的空闲内存中分配内存。它们在多大程度上受操作系统或语言运行时控制?
当线程创建时,操作系统为每个系统级线程分配堆栈。通常,语言运行时会调用操作系统来为应用程序分配堆。
可以在这里找到更多信息。
它们的范围是什么?
已在顶部给出。
"如果您知道编译时需要分配多少数据,并且数据量不太大,则可以使用堆栈。如果您不知道运行时需要分配多少数据,或者需要分配大量数据,则可以使用堆。"
可以在这里找到更多信息。
当线程创建时,操作系统设置堆栈的大小。堆的大小在应用程序启动时设置,但它可以随着需要增长(分配器从操作系统请求更多内存)。是什么决定了它们的大小?
堆栈分配要快得多,因为它实际上只是移动堆栈指针。使用内存池,您可以获得与堆分配相当的性能,但这会带来一些额外的复杂性和自身的问题。什么能让代码更快?
rlimit_stack
等系统变量和行为的某些方面。另请参见 Red Hat 的 Issue 1463241。 - jww