- 栈和堆是什么?
- 它们在计算机内存中的物理位置在哪里?
- 它们在多大程度上受操作系统或语言运行时的控制?
- 它们的作用范围是什么?
- 是什么决定它们的大小?
- 是什么使它们更快?
虽然主要内容已经涵盖了,但我还有一些需要分享的。
堆栈
堆
有趣的事实:
有些答案过于苛刻,我来做出一点贡献。
令人惊讶的是,没有人提到多个(即不与运行的操作系统级线程数量相关)调用堆栈不仅存在于奇特的语言(PostScript)或平台(Intel Itanium)中,还存在于纤程、绿色线程和某些协同程序实现中。
纤程、绿色线程和协同程序在很多方面都相似,这导致了很多混乱。纤程和绿色线程之间的区别在于前者使用协作式多任务处理,而后者可能具有协作式或抢占式多任务处理(甚至两者都有)。关于纤程和协同程序之间的区别,请参见此处。
无论如何,纤程、绿色线程和协同程序的目的都是在单个操作系统级线程内同时执行多个函数,但不是并行执行(有关区别,请参见此 SO 问题),它们通过有组织的方式相互之间转移控制。
使用纤程、绿色线程或协同程序时,通常每个函数都有一个单独的堆栈。(严格来说,不仅是堆栈,还包括整个执行上下文。最重要的是,CPU 寄存器。)对于每个线程,存在与并发运行的每个函数一样多的堆栈,并且线程按照程序逻辑在这些函数之间进行切换。当函数运行到其结束时,其堆栈被销毁。因此,堆栈数量和生命周期是动态的,不取决于操作系统级线程的数量!
请注意我说“通常每个函数都有一个单独的堆栈”。协程有“有栈”和“无栈”两种实现方式。最著名的C++有栈实现是Boost.Coroutine和Microsoft PPL的async/await
。 (但是,C ++的可恢复函数(即“ async
”和await
“)被建议用于C++17,则可能使用无栈协程。)
C ++标准库的纤程提议即将出现。此外还有一些第三方库。在像Python和Ruby这样的语言中,绿色线程非常流行。
哇!这么多答案,我觉得没一个是正确的...
1)它们在哪里,以及它们(在实际计算机内存中)是什么?
栈是一块内存,它开始于分配给程序图像的最高内存地址,然后从那里递减。它保留用于调用函数参数和在函数中使用的所有临时变量。
有两个堆:公共堆和私有堆。
私有堆始于您程序代码中最后一个字节之后的 16 字节边界(对于 64 位程序)或 8 字节边界(对于 32 位程序),然后增加。也称为默认堆。
如果私有堆过大,则会与栈区重叠,就像栈过大时将重叠堆一样。因为栈从更高的地址开始并向低地址运行,借助适当的黑客技巧,您可以使栈变得非常大,以至于它将超出私有堆区域并重叠代码区域。然后的技巧是重叠足够的代码区域,以便您可以钩入代码。这有点棘手且可能导致程序崩溃,但很容易且非常有效。
公共堆驻留在自己的内存空间中,超出您的程序图像空间。如果内存资源不足,它将被吸进硬盘。
2)操作系统或语言运行时控制它们的程度如何?
栈由程序员控制,私有堆由操作系统管理,公共堆不受任何人控制,因为它是一个操作系统服务 - 您发出请求,然后请求可能被授予或拒绝。
2b) 它们的作用域是什么?
它们都是程序全局变量,但其内容可以是私有的、公共的或全局的。
2c) 什么决定了它们各自的大小?
栈和私有堆的大小由编译器运行时选项决定。公共堆在运行时使用大小参数进行初始化。
2d) 什么使得其中一个更快?
它们并没有被设计成快速的,它们被设计成有用的。程序员如何利用它们决定它们是“快”还是“慢”。
REF:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://learn.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://learn.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate
它们在哪里? 它们在计算机内存的物理位置是什么?
答案: 它们都位于RAM中。
ASIDE:
RAM就像一张桌子,而HDDs/SSDs (永久存储)则像书架。要阅读任何内容,您必须在桌子上打开一本书,您只能打开适合您桌子大小的书。要获取一本书,您从书架上取出并在桌子上打开它。要归还一本书,您关闭桌子上的书并将其放回书架。
堆栈和堆是编译器用于在同一位置(即RAM中)存储不同类型数据的方式所给出的名称。
它们的范围是什么?
什么决定了它们每个的大小?
什么使它们中的一个更快?
答案:
堆栈用于静态(固定大小)数据
a. 在编译时,编译器会读取代码中使用的变量类型。
i. 为这些变量分配一定数量的内存。
ii. 这个内存的大小无法增长。
b. 内存是连续的(单个块),因此访问 有时 比堆更快
c. 在运行时超过堆栈大小并导致堆栈溢出错误的堆栈对象会被放置在堆上
堆是用于动态(可变大小)数据的
a. 内存使用量仅受RAM中可用空闲空间的限制
i. 在运行时,使用量可以根据需要增加或缩小
b. 由于在RAM中查找空闲空间来分配项目,因此数据不总是在连续的部分,这有时会导致访问比堆栈慢。
c. 程序员使用new
关键字将项目手动放入堆中,并且必须在使用完毕后手动释放此内存。
i. 重复分配新内存而不在不再需要时释放它的代码会导致内存泄漏。
附注:
堆和堆栈的主要引入目的不是为了提高速度,而是为了处理内存溢出。使用堆与堆栈之间的主要考虑应该是是否会发生内存溢出。如果对象的大小可能会增长到未知数量(例如链表或其成员可以容纳任意数量数据的对象),则将其放置在堆上。尽可能使用C++标准库(STL)容器vector、map和list,它们既具有内存效率又具有速度效率,并且添加它们是为了让您的生活更轻松(您不需要担心内存分配/释放)。
在让代码能够运行后,如果您发现它的运行速度不可接受,那么回过头来重构您的代码并查看是否可以更加高效地编写程序。问题可能根本与堆栈或堆没有直接关系(例如,使用迭代算法而不是递归算法,考虑I/O与CPU绑定任务,也许添加多线程或多进程等)。
我之前说“有时候”快慢是因为程序的速度可能与在堆栈或堆上分配项目无关。
在多大程度上由操作系统或语言运行时来控制?
答案:
堆栈大小由编译器在编译时确定。
堆的大小在运行时变化。 (堆在运行时与操作系统一起工作以分配内存。)
附注:
以下是有关控制和编译时间与运行时操作的更多信息。
每台计算机都有一个独特的指令集架构(ISA),这是其硬件命令(例如“MOVE”,“JUMP”,“ADD”等)。
操作系统只是资源管理器(控制何时、如何以及在何处使用内存、处理器、设备和信息)。
操作系统(OS)的ISA被称为裸机(bare machine),其余命令被称为扩展机器(extended machine)。内核是扩展机器的第一层。它控制诸如:
当我们说“编译器”时,我们通常指的是编译器、汇编器和链接器的组合。
当执行时,机器码被传递到内核中,内核确定何时运行和接管控制,但机器码本身包含用于请求文件、请求内存等的ISA指令。因此,代码发出ISA指令,但所有内容都必须通过内核。
malloc
是一个内核调用吗? - Peter Mortensen所以当我们在一个方法中使用new关键字时,引用(一个整数)会在堆栈中创建,但对象及其所有内容(包括值类型和对象)会在堆中创建,如果我没记错的话。但本地基本值类型和数组是在堆栈中创建的。
内存访问的差异在于单元格引用级别:访问堆需要更多的复杂性来处理CPU寄存器,而堆栈则“更”局部,因为CPU堆栈寄存器用作基地址,如果我没记错的话。
这就是为什么当我们有非常长或无限递归调用或循环时,我们会迅速遇到堆栈溢出,在现代计算机上不会让系统冻结的原因...
C# Heap(ing) Vs Stack(ing) In .NET
Stack vs Heap: Know the Difference
Static class memory allocation where it is stored C#
https://en.wikipedia.org/wiki/Memory_management
https://en.wikipedia.org/wiki/Stack_register
汇编语言资源:
非常感谢您进行了一次非常好的讨论,但作为一个真正的新手,我想知道指令存储在哪里?在最初,科学家们在决定两种架构(von NEUMANN,其中一切都被视为数据和HARVARD,其中一个内存区域被保留用于指令和另一个用于数据)之间进行选择。最终,我们选择了von Neumann设计,现在一切都被认为是“相同的”。当我学习汇编语言https://www.cs.virginia.edu/~evans/cs216/guides/x86.html时,这使我感到困难,因为他们谈论寄存器和堆栈指针。
以上所有内容都涉及数据。我的猜测是,由于指令是具有特定内存占用的定义明确的事物,因此它将进入堆栈,因此在汇编中讨论的所有“那些”寄存器都在堆栈上。当然,接下来出现了面向对象编程,其中指令和数据混合成动态结构,因此指令也将保存在堆上?
rlimit_stack
等系统变量和行为的某些方面。另请参见 Red Hat 的 Issue 1463241。 - jww