关于内存管理:堆栈和堆在哪里?

What and where are the stack and heap?

编程语言书籍解释了值类型是在堆栈上创建的,引用类型是在堆上创建的,而没有解释这两个东西是什么。对此我还没有读到清楚的解释。我明白什么是堆栈。但是,

  • 它们在哪里和什么地方(物理上在真正的计算机内存中)?
  • 它们在多大程度上受操作系统或语言运行时控制?
  • 他们的范围是什么?
  • 它们的大小是由什么决定的?
  • 是什么让你跑得更快?

  • 这里可以找到一个很好的解释:堆栈和堆之间的区别是什么?
  • 另外(真的)很好:codeproject.com/articles/76153/…(堆栈/堆部分)
  • YouTube.com/观看?v=cloudvddzim&spfreload=5
  • 相关,请参见堆栈冲突。堆栈冲突修正影响了系统变量和行为的某些方面,如rlimit_stack。另见Red Hat发行版1463241。
  • @堆栈和堆的定义不依赖于任何值和引用类型。换句话说,即使值和引用类型从未存在,也可以完全定义堆栈和堆。此外,在理解值和引用类型时,堆栈只是一个实现细节。Per-Eric Lippert:堆栈是一个实现细节,第一部分。


堆栈是为执行线程留出的临时空间的内存。调用函数时,在堆栈顶部为局部变量和一些簿记数据保留一个块。当该函数返回时,块将变为未使用状态,可以在下次调用函数时使用。堆栈总是按后进先出顺序保留;最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单;从堆栈中释放块只需调整一个指针即可。

堆是为动态分配预留的内存。与堆栈不同,没有强制模式来分配和释放堆中的块;您可以随时分配一个块,并随时释放它。这使得跟踪在任何给定时间分配或释放堆的哪些部分变得更加复杂;有许多自定义堆分配器可用于调整不同使用模式的堆性能。

每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管对于不同类型的分配有多个堆并不少见)。

要直接回答您的问题:

To what extent are they controlled by the OS or language runtime?

创建线程时,操作系统为每个系统级线程分配堆栈。通常,语言运行时调用操作系统来为应用程序分配堆。

What is their scope?

堆栈连接到线程,因此当线程退出时,堆栈将被回收。堆通常在应用程序启动时由运行时分配,并在应用程序(技术流程)退出时回收。

What determines the size of each of them?

创建线程时设置堆栈大小。堆的大小在应用程序启动时设置,但可以随着空间的需要而增长(分配器从操作系统请求更多内存)。

What makes one faster?

堆栈更快,因为访问模式使得从中分配和解除分配内存变得很简单(指针/整数只是简单地递增或递减),而堆在分配或解除分配中涉及更复杂的簿记。此外,堆栈中的每个字节往往被非常频繁地重用,这意味着它往往被映射到处理器的缓存,从而使其非常快速。堆的另一个性能问题是,堆主要是全局资源,通常必须是多线程安全的,即每个分配和释放都需要(通常)与程序中的"所有"其他堆访问同步。

清晰的演示:><br /><sub>image source:vikashazrati.wordpress.com<sub></P></p>
<div class=

  • 很好的答案-但是我认为您应该补充一点,当进程启动时(假设存在OS),堆栈由OS分配,它由程序内联维护。这也是堆栈速度更快的另一个原因——推送和弹出操作通常是一条机器指令,而现代机器在一个周期内至少可以执行其中的三条指令,而分配或释放堆则需要调用操作系统代码。
  • 最后的图表让我很困惑。我以为我得到了它,直到我看到那个图像。
  • @A释放处理器运行有或没有操作系统的指令。我最关心的一个例子是snes,它没有API调用,没有我们今天所知道的操作系统——但是它有一个栈。在堆栈上分配是对这些系统的加和减,这对于那些从创建它们的函数返回时被破坏的变量来说是很好的,但是要将其转换为,例如,一个构造函数,其结果不能被丢弃。为此,我们需要堆,它与调用和返回无关。大多数操作系统都有一个API堆,没有理由自己去做。
  • "堆栈是留作临时空间的内存"。酷。但在Java内存结构方面,它实际上在哪里"搁置"呢?是堆内存/非堆内存/其他(Java内存结构,按BETSOL.COM/2017/06/Helip;
  • JavaStasHooJava运行时,作为字节码解释器,添加了一个更高级别的虚拟化,所以您所提到的只是Java应用程序的观点。从操作系统的角度来看,所有这些都只是一个堆,Java运行时进程将它的一些空间分配为处理字节码的"非堆"内存。其余的操作系统级堆用作应用程序级堆,对象的数据存储在该堆中。
  • 所以,堆栈是操作系统级别的"预留"堆。堆栈由线程使用CPU指令直接管理,堆是由操作系统映射的常规内存区域。


堆栈:

  • 像堆一样存储在计算机RAM中。
  • 在堆栈上创建的变量将超出作用域并自动释放。
  • 与堆中的变量相比,分配要快得多。
  • 使用实际的堆栈数据结构实现。
  • 存储本地数据、返回地址,用于参数传递。
  • 当使用太多的堆栈时(主要来自无限递归或太深递归,非常大的分配),可能会出现堆栈溢出。
  • 在堆栈上创建的数据可以在没有指针的情况下使用。
  • 如果您知道在编译时间之前需要分配多少数据,并且数据不太大,那么您将使用堆栈。
  • 通常在程序启动时已经确定了最大大小。

堆:

  • 像堆栈一样存储在计算机RAM中。
  • 在C++中,堆上的变量必须手动销毁,并且永远不会超出范围。使用deletedelete[]free释放数据。
  • 与堆栈上的变量相比,分配速度较慢。
  • 按需分配数据块供程序使用。
  • 当存在大量分配和释放时,可能会有碎片。
  • 在C++或C中,堆上创建的数据将由指针指向,并分别用EDCOX1×3或EDCOX1×4来分配。
  • 如果请求分配的缓冲区太大,则可能会出现分配失败。
  • 如果您不知道在运行时需要多少数据,或者需要分配大量数据,那么可以使用堆。
  • 负责内存泄漏。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

  • 指针pbuffer和b的值位于堆栈上,很可能分配给函数的入口。根据编译器的不同,也可以在函数入口分配缓冲区。
  • 人们普遍误解,根据C99语言标准(可从open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf获得)的定义,C语言需要"堆栈"。事实上,"堆栈"一词甚至没有出现在标准中。这回答了对C的堆栈使用的语句,一般来说是正确的,但语言不要求这样做。有关更多信息,请参阅knosf.co.uk/cbook/cbook.html,尤其是如何在奇数球架构(如en.wikipedia.org/wiki/burroughs_large_systems)上实现C
  • @Brian您应该解释为什么在堆栈上创建buffer[]和pbuffer指针,以及为什么在堆上创建pbuffer的数据。我认为一些PPL可能会被您的答案弄糊涂,因为他们可能认为程序专门指示在堆栈和堆上分配内存,但事实并非如此。是因为缓冲区是值类型,而pbuffer是引用类型吗?
  • @黑石:1岁。这已经被覆盖了:在堆栈上创建的变量将超出作用域并自动解除分配。Re 2。这已经被覆盖了:堆中的变量必须手动销毁,并且永远不会超出范围。使用delete、delete[]或free释放数据
  • @布赖恩,我是个新手,我有一个关于这个话题的问题。如果我以某种方式声明一个变量,它是在堆栈上还是在堆上?声明的变量不在其他位置?静态成员呢?
  • @邦迪,你能解释一下,为什么在堆中分配变量比较慢??与堆栈比较。
  • 我认为,因为在堆栈上分配某些内容只是增加堆栈指针的顶部。对于堆,您需要找到一个足够大的可用位置。虽然不太确定,但我知道堆栈要快得多。
  • @邦迪,你能解释一下什么导致变量被分配到堆上吗?似乎隐式地,声明为指针的变量是在堆上分配的,否则是在堆栈上分配的。但是,情况总是这样吗?
  • @移除器:没有指针持有地址,它可以平等地指向堆或堆栈上的某个内容。new、malloc和其他一些类似于malloc的函数在堆上分配,并返回分配的内存的地址。为什么要在堆上分配?这样你的内存就不会超出范围,直到你想释放它。
  • "负责内存泄漏"-堆不负责内存泄漏!懒惰/健忘/前Java编码器/编码器谁不给垃圾!
  • 此外,有关范围和分配的注释是错误的-范围根本没有连接到堆栈或堆。堆中的变量必须手动销毁,并且永远不会超出范围。不正确;更正确的说法是,"当引用堆的变量超出范围时,堆上的数据不会被释放。"由你(或垃圾收集者)来释放它们。
  • 读它其他的答案"计算机内存只是一系列地址;"堆"和"栈"是编译器的发明。"是真的吗?
  • ?如果您不知道运行时需要多少数据,或者需要分配大量数据,那么可以使用堆。还可以控制对象的生存期
  • @邦迪,你好,我有一个疑问,如果我在堆上分配内存,但没有释放内存怎么办?那么,程序终止后,内存会被释放吗?
  • 所以栈和堆是抽象的,显然是用真实的数据结构创建的。但您缺少一点,谁创建并负责堆栈和堆的生命周期?是操作系统吗?这些堆栈和堆究竟是如何创建的?如果您不想将此信息直接包含在您的答案中,您可以链接到一本书或一个详细解释此问题的来源吗?而且,您的枚举可能会更好,枚举点的排序方式也更符合逻辑!
  • @Rohisaluja大多数操作系统在程序终止时释放未释放的内存,但最好的方法还是显式释放所有动态分配的内存。
  • 堆栈不由垃圾收集器管理。因此,必须手动分配或释放内存。
  • 一个小的/特殊的情况,在处理器中注册-对于C/C++编译器,如果检测到一个"栈"变量的生命周期很短,或者代码可以被适当地优化,一个变量可能永远不会进入用户可能认为的"内存",而是在它自己的少量工作寄存器中真正地靠近处理器。通常情况下,用于"索引"到一个事物数组中的值是这样的,因为,特别是对于复杂的指令集处理器,它们具有使用这些寄存器进行此类操作的快速/有效的操作代码!


最重要的一点是堆和堆栈是内存分配方式的通用术语。它们可以以许多不同的方式实现,并且术语适用于基本概念。好的。

  • 在一堆项目中,项目按其放置的顺序一个接一个地放置在另一个项目上,您只能移除顶部的项目(而不将整个项目倾倒)。好的。

    Stack like a stack of papers好的。

    堆栈的简单性在于,您不需要维护包含已分配内存的每个部分的记录的表;您需要的唯一状态信息是指向堆栈末尾的单个指针。要分配和取消分配,只需递增和递减那个单指针。注意:有时可以实现一个堆栈,从内存段的顶部开始向下扩展,而不是向上增长。好的。

  • 在堆中,项目的放置方式没有特定的顺序。您可以按任何顺序访问和删除项目,因为没有明确的"top"项目。好的。

    Heap like a heap of licorice allsorts好的。

    堆分配要求维护一个完整的记录,记录分配的内存和没有分配的内存,以及一些开销维护,以减少碎片,找到足够大的连续内存段以适合请求的大小,等等。内存可以随时释放,留下可用空间。有时,内存分配器会执行维护任务,例如通过移动分配的内存来对内存进行碎片整理,或者垃圾收集—在运行时识别内存不再在作用域内并解除分配。好的。

这些图像应该能够很好地描述在堆栈和堆中分配和释放内存的两种方法。百胜!好的。

  • 它们在多大程度上受操作系统或语言运行时的控制?好的。

    如前所述,堆和堆栈是通用术语,可以通过多种方式实现。计算机程序通常有一个称为调用堆栈的堆栈,它存储与当前函数相关的信息,例如指向从哪个函数调用它的指针,以及任何局部变量。因为函数调用其他函数,然后返回,所以堆栈会增长和收缩,以将来自函数的信息保存在调用堆栈的下方。一个程序并没有真正的运行时控制权;它是由编程语言、操作系统甚至系统体系结构决定的。好的。

    堆是一个通用术语,用于动态和随机分配的任何内存,即无序分配。内存通常由操作系统分配,应用程序调用API函数来进行分配。管理动态分配的内存(通常由操作系统处理)需要一定的开销。好的。

  • 他们的范围是什么?好的。

    调用堆栈是一个低级概念,在编程的意义上它与"范围"没有关系。如果反汇编一些代码,您将看到对堆栈部分的相对指针样式引用,但是对于更高级别的语言而言,该语言强制使用自己的作用域规则。然而,堆栈的一个重要方面是,一旦函数返回,该函数的任何局部内容都将立即从堆栈中释放。考虑到您的编程语言是如何工作的,这是您期望的工作方式。在堆中,也很难定义。范围是操作系统公开的任何内容,但是您的编程语言可能会添加有关应用程序中的"范围"的规则。处理器体系结构和操作系统使用虚拟寻址,处理器将其转换为物理地址,并存在页面错误等。它们跟踪哪些页面属于哪些应用程序。不过,您不必担心这一点,因为您只需使用编程语言使用的任何方法来分配和释放内存,并检查错误(如果分配/释放由于任何原因失败)。好的。

  • 它们的大小是由什么决定的?好的。

    同样,它取决于语言、编译器、操作系统和体系结构。堆栈通常是预先分配的,因为根据定义,它必须是连续的内存(更多内容在最后一段中)。语言编译器或操作系统决定其大小。您不需要在堆栈上存储大量的数据,因此它足够大,不应该完全使用它,除非出现不需要的无休止递归(因此称为"堆栈溢出")或其他异常的编程决策。好的。

    堆是可以动态分配的任何内容的通用术语。根据你对它的看法,它的大小在不断变化。不管怎样,在现代处理器和操作系统中,它的确切工作方式是非常抽象的,因此通常您不需要太担心它是如何工作的,除非(在允许您使用的语言中)您不能使用尚未分配的内存或已释放的内存。好的。

  • 是什么让你跑得更快?好的。

    堆栈更快,因为所有可用内存总是连续的。不需要维护所有空闲内存段的列表,只需要一个指向当前堆栈顶部的指针。为此,编译器通常将此指针存储在一个特殊的快速寄存器中。更重要的是,栈上的后续操作通常集中在非常近的内存区域中,而在非常低的级别上,这对于死缓存上的处理器进行优化是很好的。好的。

好啊。

  • 堆栈的图像错误;它应该类似于thermo box.co.uk/image s/stories/finiw/&hellip;这就是为什么它也被称为"下推堆栈"的原因。
  • 大卫,我不同意这是一个好的形象,或者说"下推堆栈"是一个很好的术语来说明这个概念。当您向堆栈中添加某些内容时,不会向下推堆栈中的其他内容,它们将保留在原来的位置。
  • google.com.au/search?Q=甘草+各种各样的
  • 这个答案包括一个大错误。堆栈上没有分配静态变量。请参阅我的答案[链接]stackoverflow.com/a/13326916/1763801了解详细信息。你将"自动"变量等同于"静态"变量,但它们完全不同
  • 具体来说,您说"静态分配的局部变量"是在堆栈上分配的。实际上,它们是在数据段中分配的。堆栈上只分配自动分配的变量(包括大多数但不是所有局部变量,以及通过值而不是通过引用传递的函数参数)。
  • 当我说"静态分配"局部变量时,我的意思是与动态分配相反。如果使用int a;启动函数,则堆栈中会出现a。如果使用int *a = (int *) malloc(32768*sizeof(int));启动函数,则该数组的内容不会在堆栈上,因为它们是动态分配的。
  • 我刚刚意识到你是对的——在C语言中,静态分配是它自己独立的东西,而不是任何非动态的东西的术语。我已经编辑了我的答案,谢谢。
  • 它不仅仅是C.Java、Pascal、Python,还有许多其他人都有静态与自动和动态分配的概念。说"静态分配"就意味着同样的事情无处不在。在任何语言中,静态分配都不意味着"非动态"。您需要为所描述的内容(即堆栈上的内容)使用术语"自动"分配。
  • 如果您想在python而不是c中看到这一点,我在下面添加了更多的代码示例:)
  • 所有的内存都是这样设计的吗,有堆栈和堆?
  • @无时间堆栈和堆是分配内存的两种常见方法,几乎可以在任何计算平台上找到。也就是说,在内存分配中还有其他概念,其中一些概念在较低(物理到虚拟内存映射)或较高(数据结构)级别上运行。
  • @托马斯鲁特,谢谢,是谁决定了我们应该以这种方式分配记忆?硬件设计师?
  • @永恒的实际原因。堆栈的工作方式是这样的,因为它对处理器来说非常快速和简单,并且它符合我们在进入函数时推送的局部变量和退出时弹出的局部变量的范例,等等。堆栈的工作方式是这样的,因为我们需要一种随机分配和取消分配内存或任意长度的方法,而不需要它是连续的,并且不需要按分配顺序反分配。
  • @永恒的CUDA就是这样一个例子,在这里堆栈和堆是非常不受欢迎的。有数千个线程,每一个线程都有一个栈是非常昂贵的。因此,大多数设备函数都是内联的,应该避免递归。堆操作,如malloc也是可能的,但由于效率原因,不鼓励这样做。最好的方法是一次性分配您需要的所有内存,然后在不进行任何分配/释放的情况下使用/重用它。


(我把这个答案从另一个或多或少是这个问题的翻版的问题中移走了。)好的。

您的问题的答案是特定于实现的,并且可能因编译器和处理器体系结构而异。然而,这里有一个简单的解释。好的。

  • 堆栈和堆都是从底层操作系统分配的内存区域(通常是按需映射到物理内存的虚拟内存)。
  • 在多线程环境中,每个线程都有自己的完全独立的堆栈,但它们将共享该堆。必须在堆上控制并发访问,而不能在堆栈上进行并发访问。

  • 堆包含已用和可用块的链接列表。堆上的新分配(由newmalloc通过从其中一个空闲块创建合适的块来满足。这需要更新堆上的块列表。有关堆上块的元信息也存储在堆上,通常存储在每个块前面的一个小区域中。
  • 随着堆的增长,新的块通常从较低的地址分配到较高的地址。因此,您可以将堆视为内存块的堆,随着内存的分配,内存块的大小会增大。如果堆太小,无法进行分配,则通常可以通过从底层操作系统获取更多内存来增加大小。
  • 分配和解除分配许多小的块可能会使堆处于这样的状态:在使用的块之间散布着许多小的空闲块。分配一个大块的请求可能会失败,因为没有一个空闲块大到足以满足分配请求,即使空闲块的组合大小可能足够大。这称为堆碎片。
  • 当释放与空闲块相邻的已用块时,可以将新的空闲块与相邻的空闲块合并,以创建更大的空闲块,从而有效地减少堆的碎片。

The heap好的。堆栈

  • 堆栈通常与CPU上名为堆栈指针的特殊寄存器紧密相连。最初,堆栈指针指向堆栈顶部(堆栈上的最高地址)。
  • CPU具有将值推送到堆栈上并从堆栈中弹出值的特殊指令。每次推送都将值存储在堆栈指针的当前位置,并减小堆栈指针。pop检索堆栈指针指向的值,然后增加堆栈指针(不要被向堆栈中添加值会减少堆栈指针并删除值会增加堆栈指针这一事实混淆)。记住,堆栈会增长到底部)。存储和检索的值是CPU寄存器的值。
  • 当调用函数时,CPU使用推送当前指令指针的特殊指令,即在堆栈上执行的代码的地址。然后,CPU通过设置指向被调用函数地址的指令指针。稍后,当函数返回时,旧的指令指针将从堆栈中弹出,并在调用函数后在代码处继续执行。
  • 当一个函数被输入时,栈指针被减少,以便在栈上为局部(自动)变量分配更多的空间。如果函数有一个本地32位变量,则在堆栈上留出四个字节。当函数返回时,堆栈指针移回以释放分配的区域。
  • 如果函数有参数,则在调用函数之前将这些参数推送到堆栈上。然后,函数中的代码能够从当前堆栈指针向上导航堆栈,以定位这些值。
  • 嵌套函数调用的工作方式很迷人。每个新调用都将分配函数参数、返回地址和局部变量的空间,这些激活记录可以为嵌套调用进行堆叠,并在函数返回时以正确的方式展开。
  • 由于堆栈是有限的内存块,您可以通过调用太多嵌套函数和/或为局部变量分配太多空间来导致堆栈溢出。通常,用于堆栈的内存区域的设置方式是这样的:在堆栈的底部(最低地址)以下写入将触发CPU中的陷阱或异常。然后,运行时可以捕获这种异常情况,并将其转换为某种堆栈溢出异常。

The stack好的。

Can a function be allocated on the heap instead of a stack?

Ok.

不,函数(即局部变量或自动变量)的激活记录被分配到堆栈上,该堆栈不仅用于存储这些变量,还用于跟踪嵌套函数调用。好的。

如何管理堆实际上取决于运行时环境。C使用EDCOX1×0,C++使用EDCOX1,1,但是许多其他语言都有垃圾收集。好的。

然而,堆栈是一个更低级的特性,与处理器体系结构紧密相连。当空间不足时增加堆并不难,因为它可以在处理堆的库调用中实现。然而,增加堆栈通常是不可能的,因为只有当堆栈溢出太迟时才会发现;关闭执行线程是唯一可行的选择。好的。好啊。

  • @马丁-一个非常好的回答/解释,而不是更抽象的公认答案。一个显示堆栈指针/寄存器被用于VIS函数调用的示例程序将更具说明性。
  • 每个引用类型都是值类型(int、string等)的组合。如前所述,值类型存储在堆栈中,而不是作为引用类型的一部分时如何工作。
  • 这个答案在我看来是最好的,因为它帮助我理解返回语句的真正含义,以及它与我不时遇到的这个"返回地址"的关系,将函数推送到堆栈上意味着什么,以及为什么将函数推送到堆栈上。伟大的回答!
  • 在我看来,这是最好的,也就是说,堆/堆栈是非常具体的实现。其他的答案假设了很多关于语言和环境/OS的事情。+ 1
  • 您的意思是"函数中的代码能够从当前堆栈指针向上导航以定位这些值。"?你能详细解释一下吗?
  • @Koraytugay:当函数开始执行堆栈顶部(因为堆栈是"反向的",所以最低地址)时,包含返回地址(ret)。函数参数(args)在堆栈中低于此值(在更高的地址)。函数参数的声明决定了堆栈上参数的布局,函数中的代码只能通过在输入函数时知道堆栈指针以及参数的确切大小和顺序来访问这些参数。所有内容都与堆栈指针相关。
  • @MartinLiverSage我以为它们是通过数组索引访问的。
  • @Koraytugay:我的解释是在CPU/机器代码/程序集级别,那里只有寄存器和内存可以寻址以读写数据。一般来说,数组是一个更高层次(编程语言)的概念。好的,CPU有向量指令,但是当您试图理解堆栈时,它们并不重要。
  • @好的,谢谢你的澄清。我试图为一种语言(为了学习目的)创建一个简单的解释程序,所以我很困惑。
  • 我想知道有多少现代程序员没有使用十六进制编辑器?如果你不能分解二进制文件来理解正在发生的事情,那么栈和堆的概念就变得不明显了。不是每个人都能在汇编中进行编程,这甚至超出了我的编程技能,但了解机器体系结构的概念以及指令集如何工作对于掌握您的行业至关重要。
  • (继续)…如今抽象程度更高的程序员与依赖自动导航系统的现代飞行员相似:当系统崩溃,需要依赖图表时,他们会迷失方向。
  • 对一些概念有很好的解释,但在我看来,这些图表确实令人困惑。
  • 堆栈上激活记录中函数的"返回地址"是什么意思
  • @返回地址是调用前的当前指令指针。我试着在第三个子弹的堆叠部分解释这一点。在函数调用之前,将当前指令指针推送到堆栈上。当函数返回时,这个地址将从堆栈中弹出,并在下一条指令中继续执行。函数调用的激活记录包含返回地址,当函数结束时,CPU应继续执行该地址。函数不知道这一点。它必须由函数的调用方提供。
  • @据我所知,引用类型的值也保存在堆中。否则,堆将主要由引用堆栈上数据的指针组成。
  • @martindeliversage:所以调用堆栈实际上是通过不同的激活记录(每个激活记录代表一个函数)保存在堆栈中的,不是吗?
  • @调用堆栈是用于实现函数调用的保留内存。每次调用都会在堆栈上创建一个新的激活记录。激活记录包含1)提供给函数的参数,2)函数结束时将继续执行的地方,以及3)函数的局部变量。激活记录"相互叠加"(因此称为名称堆栈),它允许对同一函数进行多个调用(递归)。当函数返回时,它的激活记录被丢弃,调用函数及其激活记录被激活。参见图2。


在下面的C代码中

1
2
3
4
5
6
public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

以下是如何管理内存的方法

Picture of variables on the stack

Local Variables,只要函数调用进入堆栈,它就需要持续。堆用于变量,这些变量的生存期我们并不预先知道,但我们希望它们能持续一段时间。在大多数语言中,如果我们想将变量存储在堆栈中,那么在编译时知道变量有多大是至关重要的。

对象(在我们更新它们时大小不同)会进入堆中,因为我们不知道它们在创建时会持续多长时间。在许多语言中,堆被垃圾收集以查找不再具有任何引用的对象(如cls1对象)。

在爪哇,大多数对象直接进入堆。在C/C++等语言中,当您不处理指针时,结构和类通常可以保留在堆栈上。

更多信息可在这里找到:

堆栈和堆内存分配的区别«;timmurphy.org

这里:

在堆栈和堆上创建对象

本文是以上图片的来源:六个重要的.NET概念:堆栈、堆、值类型、引用类型、装箱和取消装箱-代码项目

但请注意,它可能包含一些不准确之处。

  • 这是不正确的。i和cls不是"静态"变量。它们被称为"局部"或"自动"变量。这是一个非常重要的区别。请参阅[链接]stackoverflow.com/a/13326916/1763801了解详细信息。
  • 我没有说它们是静态变量。我说过int和cls1是静态项。它们的内存是静态分配的,因此它们进入堆栈。这与一个需要动态内存分配的对象形成了鲜明的对比,而动态内存分配因此会进入堆中。
  • 我引用"静态项目…继续堆叠"。这完全是错误的。静态项进入数据段,自动项进入堆栈。
  • 如果您想要一个引用,这里是:en.wikipedia.org/wiki/static_variable。
  • 另外,编写代码项目文章的人也不知道他在说什么。例如,他说"原始的需要静态类型的内存",这是完全不真实的。没有什么能阻止您动态地分配堆中的原语,只需编写类似"int array[]=new int[num]"和voila的代码,原语在.NET中动态分配。这只是几个不准确的地方之一。
  • 如果你检查我的代表,你会发现我还不能投反对票。所以即使是我也不会投反对票。
  • @雪崩,在你的答案开始时This helps:链接断了。请改正。谢谢。
  • 你用什么工具来画这个图像?
  • 我编辑了你的文章,因为你对堆栈和堆中的内容犯了严重的技术错误。
  • @雪崩:关于你的照片的一个问题——在分配y之后,我如何访问i?我必须弹出y吗?交换他们?如果有很多局部变量将它们分隔开呢?
  • @困惑0你有这个问题的答案吗?我认为它们都存储在类似数组的结构中,在编译时被推送到操作数堆栈,对吗?
  • @罗伯坦德泽朱克感谢你指出这一点。固定链接。


堆栈当您调用一个函数时,该函数的参数加上一些其他开销就会放到堆栈上。一些信息(如返回时要去哪里)也存储在那里。当您在函数内部声明一个变量时,该变量也会在堆栈上分配。

解除堆栈的分配非常简单,因为您总是按与分配相反的顺序解除。当您输入函数时会添加堆栈内容,当您退出函数时会删除相应的数据。这意味着,除非调用许多调用其他函数(或创建递归解决方案)的函数,否则您倾向于停留在堆栈的一个小区域内。

机甲废场堆是一个通用名称,用于动态放置创建的数据。如果您不知道您的程序将要创建多少宇宙飞船,您可能会使用新的(或malloc或等效的)操作符来创建每个宇宙飞船。这种分配将持续一段时间,因此我们很可能会以不同于我们创建的顺序释放事物。

因此,堆要复杂得多,因为最终会有一些未使用的内存区域与块交错,这些块是内存碎片。找到所需大小的空闲内存是一个困难的问题。这就是应该避免使用堆的原因(尽管仍然经常使用堆)。

实施堆栈和堆的实现通常由运行时/OS来完成。通常情况下,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部分发,以避免依赖操作系统获取内存。

只有当你的内存使用与正常情况有很大的不同时,这才是可行的——也就是说,当你在一个巨大的操作中加载一个级别,然后在另一个巨大的操作中丢弃整个级别的游戏。

内存中的物理位置这一点没有你想象的那么重要,因为一种叫做虚拟内存的技术使你的程序认为你可以访问物理数据在其他地方(甚至硬盘上)的某个地址。。当调用树变深时,为堆栈获取的地址将按升序排列。堆的地址是不可预测的(即特定于实现),坦率地说,并不重要。

  • 避免使用堆的建议非常强烈。现代系统有良好的堆管理器,现代动态语言广泛地使用堆(程序员不必担心它)。我会说使用堆,但是使用手动分配器时,不要忘记释放!
  • 如果可以使用堆栈或堆,请使用堆栈。如果你不能使用堆栈,那就别无选择。我经常使用这两种方法,当然也会使用std::vector或类似的方法。对于新手来说,您可以避免堆,因为堆非常简单!!
  • 如果您的语言不实现垃圾收集,那么智能指针(围绕指针的单独分配对象,该指针对动态分配的内存块进行引用计数)与垃圾收集密切相关,是以安全、无泄漏的方式管理堆的一种不错的方法。它们是在各种框架中实现的,但对于您自己的程序来说也不难实现。
  • "这就是应该避免堆的原因(尽管它仍然经常被使用)。"我不确定这实际上意味着什么,特别是在许多高级语言中内存管理方式不同的情况下。由于这个问题被标记为语言不可知论,所以我想说这个特定的评论/行位置不对,不适用。
  • good point@jonnohampson-虽然你说得很有道理,但我认为如果你使用的是GC的"高级语言",那么你可能根本不关心内存分配机制,因此甚至不关心堆栈和堆是什么。


为了澄清,这个答案有错误的信息(托马斯在评论后修正了他的答案,酷:)。其他答案只是避免解释静态分配的含义。因此,我将解释三种主要的分配形式,以及它们通常如何与下面的堆、堆栈和数据段相关。我还将展示C/C++和Python中的一些例子来帮助人们理解。

堆栈上没有分配"static"(即静态分配)变量。不要这样认为——很多人这样做只是因为"静态"听起来很像"堆栈"。它们实际上既不存在于堆栈中,也不存在于堆栈中。是所谓的数据段的一部分。

但是,通常最好考虑"范围"和"生存期",而不是"堆栈"和"堆"。

范围是指代码的哪些部分可以访问变量。一般来说,我们考虑本地作用域(只能由当前函数访问)和全局作用域(可以在任何地方访问),尽管作用域可能变得更加复杂。

生存期是指在程序执行期间分配和释放变量的时间。通常我们考虑静态分配(变量将在程序的整个持续时间内持续存在,使它对跨多个函数调用存储相同的信息非常有用)与自动分配(变量仅在对函数的单个调用期间持续存在,使其对存储仅在func期间使用的信息非常有用与动态分配(变量的持续时间是在运行时定义的,而不是像静态或自动这样的编译时)相比,操作和可以在完成后丢弃。

尽管大多数编译器和解释器在使用堆栈、堆等方面都实现了类似的行为,但如果编译器想要正确的行为,有时可能会破坏这些约定。例如,由于优化,局部变量可能只存在于寄存器中,或者完全被删除,即使大多数局部变量存在于堆栈中。正如在一些注释中指出的,您可以自由地实现一个编译器,它甚至不使用堆栈或堆,而是使用其他一些存储机制(很少这样做,因为堆栈和堆非常适合这样做)。

我将提供一些简单的带注释的C代码来说明所有这些。最好的学习方法是在调试器下运行程序并观察其行为。如果您喜欢阅读python,请跳到答案的末尾:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

//"someArgument" is allocated on the stack each time MyFunction is called
//"someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be"leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

区分生存期和作用域之所以重要的一个特别令人痛心的例子是变量可以有局部作用域,但可以有静态生存期——例如,上面代码示例中的"somelocalstaticvariable"。这样的变量会使我们常见但非正式的命名习惯变得非常混乱。例如,当我们说"局部"时,我们通常是指"局部范围的自动分配变量",当我们说"全局"时,我们通常是指"全局范围的静态分配变量"。不幸的是,当涉及到"文件范围静态分配变量"之类的事情时,很多人只是说…呵呵???".

C/C++中的一些语法选择加剧了这个问题,例如许多人认为全局变量不是静态的,因为下面的语法。

1
2
3
4
int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

注意,在上面的声明中放入关键字"static"可以防止var2具有全局范围。然而,全局var1具有静态分配。这不是直觉!因此,在描述范围时,我尽量不要使用"static"这个词,而是说一些类似"file"或"file limited"的范围。然而,许多人使用短语"static"或"static scope"来描述只能从一个代码文件访问的变量。在生存期的上下文中,"静态"始终意味着变量在程序启动时分配,在程序退出时释放。

有些人认为这些概念是C/C++特定的。它们不是。例如,下面的python示例说明了所有三种类型的分配(在解释语言中可能存在一些细微的差异,我在这里不讨论)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's" + str(curTime) +", you should feed me. My favorite food is" + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ =="__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

  • 我将引用一个在函数中声明为只有局部可访问性的静态变量,但通常不会对其使用术语"scope"。另外,值得注意的是,语言基本上没有灵活性的单堆栈/堆方面:在堆栈上保存执行上下文的语言不能使用相同的堆栈来保存需要比创建它们的上下文更持久的内容。有些语言如PostScript有多个堆栈,但有一个"堆",其行为更像一个堆栈。
  • @这一切都是有道理的。我将范围定义为"代码的哪些部分可以访问变量"(并认为这是最标准的定义),因此我认为我们同意:)
  • 我认为变量的"范围"是由时间和空间所限定的。只要对象存在,就需要类对象范围中的变量保存其值。只要执行仍在执行上下文中,就需要执行上下文范围内的变量保持其值。静态变量声明创建一个标识符,该标识符的作用域被绑定到当前块,该块被附加到作用域未被绑定的变量上。
  • @supercat这就是为什么我使用lifetime这个词,也就是我如何称呼时间范围。它减少了用如此多的含义来超载"范围"这个词的需要。据我所知,即使是在规范的来源中,对于确切的定义似乎也没有完全的共识。我的术语部分来源于K&R,部分来源于我在第一个C系学习/教学时的常用用法。总是很高兴听到另一个知情的观点。
  • 一个非常常见的混淆源是"变量"是指标识符、存储位置,还是(对于面向对象框架)由存储位置的当前内容标识的堆对象。自动变量是当执行进入一个特定的范围时就已经存在的存储位置,并且在之后就不再存在了。静态变量只能在其作用域内按名称寻址,但在许多语言中,函数可以返回指向静态变量的指针,并且对于具有该指针副本的任何上下文中的代码……
  • …为了能够随意访问变量,无论当前执行上下文的任何方面是否与定义变量的函数有关。
  • 你一定是在开玩笑。你真的能在函数内部定义静态变量吗?
  • @Zaeemsattar绝对是这样的,这在C代码中并不常见。
  • @zaeemsattar将静态函数变量视为隐藏的全局变量或私有静态成员变量。


其他人的笔画回答得很好,所以我会介绍一些细节。

  • 堆栈和堆不需要是单一的。一种常见的情况是,如果一个进程中有多个线程,那么就有多个堆栈。在这种情况下,每个线程都有自己的堆栈。您也可以有多个堆,例如,某些DLL配置可能导致不同堆分配不同的DLL,这就是为什么释放由不同库分配的内存通常是一个坏主意。

  • 在C中,您可以通过使用alloca获得可变长度分配的好处,alloca在堆栈上分配,而alloc在堆上分配。这个内存在您的RETURN语句中无法生存,但它对于草稿缓冲区很有用。

  • 在不经常使用的窗口上创建一个巨大的临时缓冲区并不是免费的。这是因为编译器将生成一个堆栈探测循环,每次输入函数时都会调用该循环以确保堆栈存在(因为Windows在堆栈末尾使用单个保护页来检测堆栈何时需要增长)。如果从堆栈末尾访问内存超过一页,则会崩溃)。例子:

  • 1
    2
    3
    4
    5
    void myfunction()
    {
       char big[10000000];
       // Do something that only uses for first 1K of big 99% of the time.
    }

    • "与alloc相反":你的意思是"与malloc相反"?
    • alloca的便携性如何?
    • @彼得莫滕森,这不是posix,可移植性没有保证。


    其他人已经直接回答了您的问题,但是在试图理解堆栈和堆时,我认为考虑传统的UNIX进程(没有线程和基于mmap()的分配器)的内存布局是很有帮助的。内存管理词汇表网页有一个此内存布局的图表。

    堆栈和堆通常位于进程虚拟地址空间的两端。堆栈在访问时自动增长,达到内核设置的大小(可以使用setrlimit(RLIMIT_STACK, ...)进行调整)。当内存分配器调用brk()sbrk()系统调用,将更多的物理内存页映射到进程的虚拟地址空间时,堆就会增长。

    在没有虚拟内存的系统中,例如一些嵌入式系统,通常会应用相同的基本布局,但堆栈和堆的大小是固定的。然而,在其他嵌入式系统中(例如基于微芯片PIC微控制器的系统),程序栈是一个独立的内存块,不能由数据移动指令寻址,只能通过程序流指令(调用、返回等)进行修改或间接读取。其他架构(如IntelItanium处理器)具有多个堆栈。从这个意义上说,堆栈是CPU体系结构的一个元素。


    堆栈是内存的一部分,可以通过几个键汇编语言指令进行操作,例如"pop"(从堆栈中删除并返回值)和"push"(将值推送到堆栈),但也可以调用(调用子例程-推送地址以返回堆栈)和返回(从子例程返回-将地址弹出并跳到它上面)。它是堆栈指针寄存器下面的内存区域,可以根据需要进行设置。堆栈还用于向子例程传递参数,以及在调用子例程之前保留寄存器中的值。

    堆是内存的一部分,由操作系统提供给应用程序,通常通过类似malloc的系统调用。在现代操作系统中,这个内存是一组只有调用进程才能访问的页面。

    堆栈的大小在运行时确定,通常在程序启动后不会增长。在C程序中,堆栈必须足够大,以容纳每个函数中声明的每个变量。堆将根据需要动态增长,但操作系统最终正在进行调用(它通常会使堆增长超过malloc请求的值,这样至少将来的一些malloc将不需要返回内核以获得更多内存。这种行为通常是可定制的)

    因为在启动程序之前已经分配了堆栈,所以在使用该堆栈之前不需要进行malloc,所以这是一个小优势。实际上,在拥有虚拟内存子系统的现代操作系统中,很难预测什么是快速的,什么是缓慢的,因为页面是如何实现的以及它们存储在哪里是一个实现细节。

    • 这里还值得一提的是,Intel对堆栈访问进行了大量优化,特别是预测函数返回的位置等。


    我想很多人在这个问题上给了你最正确的答案。

    然而,有一个遗漏的细节是,"堆"实际上应该称为"空闲存储"。这种区别的原因是原始的空闲存储是用一种称为"二项式堆"的数据结构实现的。因此,从malloc()/free()的早期实现中分配是从堆中分配的。然而,在今天,大多数自由存储都是用非常复杂的数据结构实现的,而不是二项式堆。

    • 另一个吹毛求疵的问题-大多数答案(很轻)都意味着C语言需要使用"堆栈"。这是一个常见的误解,尽管它是(到目前为止)实现EDOCX1(变量)的主导范式。实际上,"stack"一词甚至没有出现在C99语言标准中:open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
    • 我对你的回答有点意见。看看这个问题的公认答案。它表示,自由存储最可能与堆相同,但不一定是。


    什么是堆栈?

    堆栈是一堆对象,通常是整齐排列的对象。

    Enter image description here

    Stacks in computing architectures are regions of memory where data is added or removed in a last-in-first-out manner.
    In a multi-threaded application, each thread will have its own stack.

    < /块引用>

    什么是堆?

    堆是杂乱无章地堆积起来的东西。

    Enter image description here

    In computing architectures the heap is an area of dynamically-allocated memory that is managed automatically by the operating system or the memory manager library.
    Memory on the heap is allocated, deallocated, and resized regularly during program execution, and this can lead to a problem called fragmentation.
    Fragmentation occurs when memory objects are allocated with small spaces in between that are too small to hold additional memory objects.
    The net result is a percentage of the heap space that is not usable for further memory allocations.

    < /块引用>

    两者合在一起

    In a multi-threaded application, each thread will have its own stack. But, all the different threads will share the heap.
    Because the different threads share the heap in a multi-threaded application, this also means that there has to be some coordination between the threads so that they don’t try to access and manipulate the same piece(s) of memory in the heap at the same time.

    < /块引用>

    哪个更快——堆栈还是堆?为什么?

    The stack is much faster than the heap.
    This is because of the way that memory is allocated on the stack.
    Allocating memory on the stack is as simple as moving the stack pointer up.

    < /块引用>

    对于刚开始编程的人来说,使用堆栈可能是个好主意,因为它更简单。< BR>因为堆栈很小,所以当您确切地知道数据需要多少内存时,或者如果您知道数据的大小非常小,您就需要使用它。< BR>当你知道你的数据需要大量的内存,或者你只是不确定你需要多少内存(比如动态数组)时,最好使用堆。

    Java内存模型

    Enter image description here

    堆栈是存储局部变量(包括方法参数)的内存区域。当涉及到对象变量时,这些仅仅是对堆上实际对象的引用(指针)。每次实例化一个对象时,都会留出一块堆内存来保存该对象的数据(状态)。由于对象可以包含其他对象,因此某些数据实际上可以保存对这些嵌套对象的引用。


    你可以用这个堆栈做一些有趣的事情。例如,您有类似alloca的函数(假设您可以绕过关于它使用的大量警告),这是malloc的一种形式,专门使用堆栈而不是堆作为内存。

    也就是说,基于堆栈的内存错误是我经历过的最糟糕的错误之一。如果使用堆内存,并且超出了所分配块的界限,则很有可能触发段错误。(不是100%:您的块可能与以前分配的块偶然相邻。)但是,由于在堆栈上创建的变量总是彼此相邻,因此越界写入可以更改另一个变量的值。我了解到,每当我觉得我的程序不再遵守逻辑法则时,它很可能是缓冲区溢出。

    • alloca的便携性如何?例如,它在Windows上工作吗?它只适用于类Unix的操作系统吗?


    简单地说,栈就是创建局部变量的地方。此外,每次调用子程序时,程序计数器(指向下一条机器指令的指针)和任何重要寄存器,有时参数会被推送到堆栈上。然后将子例程中的任何局部变量推送到堆栈上(并从堆栈中使用)。当子例程完成时,这些内容都会从堆栈中弹出。PC和寄存器数据得到并放回它原来的位置,因为它是弹出的,所以你的程序可以继续其愉快的方式。

    堆是内存的一个区域,动态内存分配是通过(显式的"new"或"allocate"调用)进行的。它是一种特殊的数据结构,可以跟踪不同大小的内存块及其分配状态。

    在"经典"系统中,RAM的布局是这样的:堆栈指针从内存底部开始,堆指针从顶部开始,并向彼此增长。如果它们重叠,则说明内存不足。不过,这不适用于现代的多线程操作系统。每个线程都必须有自己的堆栈,这些堆栈可以动态创建。

    • [@T.E.D.]你为什么说"有时参数会被推到堆栈上"?我知道他们一直都是。请详细说明一下好吗?
    • @我这么说是因为它完全取决于编译器/解释器的编写者,当调用子例程时会发生什么。典型的Fortran行为是根本不使用堆栈。有些语言支持诸如pass-by-name之类的外来事物,这实际上是一种文本替换。


    来自wikianwser。

    当一个函数或方法调用另一个函数,而该函数又调用另一个函数等时,所有这些函数的执行都将保持挂起状态,直到最后一个函数返回其值为止。

    这个挂起的函数调用链就是堆栈,因为堆栈中的元素(函数调用)相互依赖。

    在异常处理和线程执行中,堆栈非常重要。

    堆只是程序用来存储变量的内存。堆(变量)的元素彼此之间没有依赖关系,可以随时随机访问。

    • "我更喜欢接受的答案,因为它的水平更低。"这是一件坏事,不是好事。


    • 快速访问
    • 不必显式地取消分配变量
    • 空间由CPU有效管理,内存不会碎片化
    • 仅限局部变量
    • 堆栈大小限制(取决于操作系统)
    • 无法调整变量大小

    • 变量可以全局访问
    • 内存大小没有限制
    • (相对)访问速度较慢
    • 没有保证有效地利用空间,随着时间的推移,内存可能会变得支离破碎,因为内存块被分配,然后被释放。
    • 您必须管理内存(您负责分配和释放变量)
    • 可以使用realloc()调整变量大小

    好吧,简单地说,简而言之,它们是指有序的,而不是有序的…!

    叠加:在叠加项中,事物相互叠加,意味着处理速度更快、效率更高!…

    所以总是有一个索引来指向特定的项目,处理也会更快,项目之间也有关系!…

    堆:没有顺序,处理速度会变慢,值会混乱在一起,没有特定的顺序或索引…有随机性,它们之间没有关系…所以执行和使用时间可能会有所不同…

    我还创建了下面的图像来显示它们的外观:

    enter image description here


    简而言之

    堆栈用于静态内存分配,堆用于动态内存分配,都存储在计算机的RAM中。好的。详细地

    堆栈好的。

    堆栈是一个"后进先出"的数据结构,由CPU管理和优化。每次函数声明一个新变量时,它都会被"推"到堆栈上。然后,每次函数退出时,该函数推送到堆栈上的所有变量都将被释放(也就是说,它们将被删除)。一旦释放一个堆栈变量,该内存区域就可用于其他堆栈变量。好的。

    使用堆栈存储变量的好处是,内存是为您管理的。你不必手动分配内存,也不必在不再需要时释放内存。更重要的是,由于CPU如此高效地组织堆栈内存,所以从堆栈变量读取和写入堆栈变量的速度非常快。好的。

    这里可以找到更多。好的。

    机甲废场好的。

    堆是计算机内存中的一个区域,它不是为您自动管理的,也不是由CPU严格管理的。它是一个更自由的浮动内存区域(并且更大)。要在堆上分配内存,必须使用malloc()或calloc(),这是内置的C函数。一旦您在堆上分配了内存,您将负责在不再需要该内存时使用free()来释放该内存。好的。

    如果您不这样做,您的程序将有所谓的内存泄漏。也就是说,堆上的内存仍将被放在一边(并且对其他进程不可用)。正如我们将在调试部分看到的,有一个叫做valgrind的工具可以帮助您检测内存泄漏。好的。

    与堆栈不同,堆对可变大小没有大小限制(除了计算机明显的物理限制)。堆内存的读取和写入速度稍慢,因为必须使用指针来访问堆上的内存。我们将很快讨论指针。好的。

    与堆栈不同,在堆上创建的变量可以被程序中任何位置的任何函数访问。堆变量在作用域中本质上是全局的。好的。

    这里可以找到更多。好的。

    堆栈上分配的变量直接存储到内存中,对该内存的访问速度非常快,并且在编译程序时处理其分配。当一个函数或方法调用另一个函数,而该函数又调用另一个函数等时,所有这些函数的执行都将保持挂起状态,直到最后一个函数返回其值为止。堆栈总是按后进先出顺序保留,最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单,从堆栈中释放块只需调整一个指针即可。好的。

    在堆上分配的变量的内存在运行时分配,并且访问该内存的速度要慢一些,但堆大小仅受虚拟内存大小的限制。堆的元素之间没有依赖关系,可以随时随机访问。您可以随时分配一个块,并随时释放它。这使得跟踪在任何给定时间分配或释放堆的哪些部分变得更加复杂。好的。

    Enter image description here好的。

    如果您确切知道在编译时间之前需要分配多少数据,并且不太大,那么可以使用堆栈。如果不知道运行时需要多少数据,或者需要分配大量数据,则可以使用堆。好的。

    在多线程情况下,每个线程都有自己的完全独立的堆栈,但它们将共享堆。堆栈是线程特定的,堆是应用程序特定的。在异常处理和线程执行中,堆栈非常重要。好的。

    每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管对于不同类型的分配有多个堆并不少见)。好的。

    Enter image description here好的。

    在运行时,如果应用程序需要更多堆,它可以从可用内存分配内存;如果堆栈需要内存,它可以从为应用程序分配的可用内存分配内存分配内存。好的。

    即使如此,这里和这里给出了更多的细节。好的。

    现在来看看你的问题答案。好的。

    To what extent are they controlled by the OS or language runtime?

    Ok.

    创建线程时,操作系统为每个系统级线程分配堆栈。通常,语言运行时调用操作系统来为应用程序分配堆。好的。

    这里可以找到更多。好的。

    What is their scope?

    Ok.

    已经在顶部给出。好的。

    "You can use the stack if you know exactly how much data you need to allocate before compile time, and it is not too big. You can use the heap if you don't know exactly how much data you will need at runtime or if you need to allocate a lot of data."

    Ok.

    在这里可以找到更多。好的。

    What determines the size of each of them?

    Ok.

    创建线程时,堆栈的大小由操作系统设置。堆的大小是在应用程序启动时设置的,但它可以随着空间的需要而增长(分配器从操作系统请求更多内存)。好的。

    What makes one faster?

    Ok.

    堆栈分配要快得多,因为它真正做的就是移动堆栈指针。使用内存池,您可以从堆分配中获得类似的性能,但这会增加一点复杂性和它自己的麻烦。好的。

    此外,stack与heap不仅要考虑性能,还可以告诉您很多关于对象预期寿命的信息。好的。

    从这里可以找到详细信息。好的。好啊。


    在20世纪80年代,Unix像兔子一样传播,大公司也在滚动。埃克森也有一个,几十个品牌的名字也被历史所遗忘。内存的布局由许多实现者自行决定。

    一个典型的C程序在内存中用通过更改brk()值增加的机会。通常,堆刚好低于此brk值而增加BRK会增加可用堆的数量。

    单个堆栈通常是堆下的一个区域,它是一个内存区。在下一个固定内存块的顶部之前不包含任何值。下一个块通常是可以被堆栈数据覆盖的代码。在它那个时代的著名黑客之一。

    一个典型的内存块是BSS(零值块)在一家制造商的产品中,它不小心被归零了。另一个是包含初始化值的数据,包括字符串和数字。第三种是包含crt(c runtime)、main、函数和库的代码。

    Unix中虚拟内存的出现改变了许多约束条件。这些块不需要连续的客观原因,或者是固定的大小,或者是现在订购的特定方式。当然,在Unix之前是不受这些约束的multics。这是一个展示那个时代记忆布局的示意图。

    A typical 1980s style UNIX C program memory layout


    虚拟内存中每个进程的堆栈、堆和数据:

    stack, heap and static data


    几美分:我想,最好是用图形化的方式来绘制内存,而且更简单:

    This is my vision of process memory construction with simplification for more easy understanding wht happening

    箭头-显示堆栈和堆的增长位置,进程堆栈大小有限制,在OS中定义,线程堆栈大小通常由线程创建API中的参数限制。堆通常按进程最大虚拟内存大小限制,例如32位2-4 GB。

    简单的方法是:进程堆是进程和内部所有线程的通用方法,在常见情况下使用它来分配内存,比如malloc()。

    堆栈是用于存储在常见情况下函数返回指针和变量中的快速内存,在函数调用、局部函数变量中作为参数进行处理。


    因为有些答案是吹毛求疵的,我要贡献我的小钱。

    令人惊讶的是,没有人提到过多个(即与正在运行的操作系统级线程的数量无关)调用堆栈不仅可以在异国语言(PostScript)或平台(Intel Itanium)中找到,而且可以在光纤、绿色线程和一些协程实现中找到。

    纤维、绿线和冠脉在很多方面都很相似,这导致了很多混乱。光纤和绿色线程之间的区别在于前者使用协作多任务,而后者可能具有协作或抢占(甚至两者都有)。有关纤维和冠脉之间的区别,请参见此处。

    在任何情况下,光纤、绿色线程和协同程序的目的都是在单个操作系统级线程内同时执行多个功能,但不并行执行(请参见本问题的区别),以有组织的方式将控制权来回转移。

    当使用光纤、绿色线程或协程时,每个函数通常有一个单独的堆栈。(从技术上讲,不只是一个堆栈,而是每个函数都有一个完整的执行上下文。最重要的是,CPU寄存器。)对于每个线程,堆栈的数量和并发运行的函数的数量一样多,线程根据程序的逻辑在执行每个函数之间切换。当一个函数运行到它的末尾时,它的堆栈就会被破坏。因此,堆栈的数量和生命周期是动态的,而不是由操作系统级线程的数量决定的!

    注意,我说过"每个函数通常有一个单独的堆栈"。Couroutine既有堆叠的实现,也有无堆叠的实现。最值得注意的StcFoost C++实现是Boost。Coroutine和微软PPL的EDOCX1 0。(然而,C++的可恢复函数(又名EDCOX1,1)和EDCOX1(2)",",这是对C++ 17提出的,很可能使用无栈协同程序。

    光纤到C++标准库的建议即将到来。还有一些第三方图书馆。绿色线程在Python和Ruby等语言中非常流行。


    我有一些东西要分享,尽管主要的要点已经被涵盖了。

    • 快速访问。
    • 存储在RAM中。
    • 函数调用与传递的局部变量和函数参数一起加载在这里。
    • 当程序超出范围时,空间会自动释放。
    • 存储在顺序存储器中。

    • 对堆栈的访问相对较慢。
    • 存储在RAM中。
    • 动态创建的变量存储在这里,稍后需要在使用后释放分配的内存。
    • 存储在内存分配完成的任何位置,始终由指针访问。

    有趣的注释:

    • 如果函数调用存储在堆中,则会导致两个混乱点:
    • 由于堆栈中的顺序存储,所以执行速度更快。堆中的存储会导致大量的时间消耗,从而使整个程序执行速度变慢。
    • 如果函数存储在堆中(指针指向的杂乱存储),就没有办法返回调用方地址(由于内存中的顺序存储,堆栈给出了这个地址)。

    • 简洁明了。尼斯:


    作为概念,很多答案都是正确的,但我们必须注意硬件(即微处理器)需要一个堆栈来允许调用子例程(以汇编语言调用)。(OOP的人会称之为方法)

    在堆栈上保存返回地址,并直接在硬件中管理调用→push/ret→pop。

    您可以使用堆栈传递参数。即使它比使用寄存器慢(微处理器专家会说还是一本80年代的好的BIOS书…)

    • 没有堆栈,微处理器就不能工作。(我们无法想象没有子程序/函数的程序,即使是汇编语言)
    • 如果没有堆,它可以。(汇编语言程序可以在没有堆的情况下工作,因为堆是一个OS概念,而malloc是一个OS/lib调用。

    堆栈使用速度更快:

    • 是硬件,甚至推/弹出都非常有效。
    • malloc需要进入内核模式,使用锁/信号量(或其他同步原语)执行一些代码,并管理一些跟踪分配所需的结构。

    • 什么是OPP?你的意思是面向对象编程吗?
    • 你是说malloc是内核调用吗?
    • 1)是的,对不起……哎呀…2)马洛克:我写的很快,对不起……malloc在用户空间中。但可以触发其他呼叫…关键是使用堆的速度可能非常慢…


    真的!答案太多了,我觉得其中一个不正确…

    1)它们在哪里和什么地方(物理上在真实的计算机内存中)?

    堆栈是以分配给程序映像的最高内存地址开始的内存,然后从中减少值。它是为被调用的函数参数和函数中使用的所有临时变量保留的。

    有两堆:公共堆和私有堆。

    私有堆从程序中最后一个字节后的16字节边界(对于64位程序)或8字节边界(对于32位程序)开始,然后从那里增加值。它也被称为默认堆。

    如果私有堆太大,它将与堆栈区域重叠;如果私有堆太大,堆栈也将与堆重叠。因为堆栈从一个更高的地址开始,一直向下一直到较低的地址,通过适当的黑客攻击,您可以使堆栈变得如此大,以至于它将超出私有堆区域并覆盖代码区域。然后,技巧是重叠足够多的代码区域,以便您能够钩住代码。这有点棘手,而且你可能会面临程序崩溃的风险,但这很容易,而且非常有效。

    公共堆驻留在程序映像空间之外的自己的内存空间中。如果内存资源稀缺的话,正是这种内存将被吸走到硬盘上。

    2)操作系统或语言运行库在多大程度上控制它们?

    堆栈由程序员控制,私有堆由操作系统管理,公共堆不受任何人控制,因为它是一个操作系统服务——您发出请求,请求被授予或拒绝。

    2b)范围是什么?

    它们对程序来说都是全局的,但是它们的内容可以是私有的、公共的或全局的。

    2c)它们的大小是由什么决定的?

    堆栈和私有堆的大小由编译器运行时选项决定。公共堆在运行时使用大小参数初始化。

    2d)什么使一个更快?

    它们的设计不是为了快速,而是为了实用。程序员如何使用它们决定了它们是"快"还是"慢"

    裁判:

    https://norasandler.com/2019/02/18/write-a-编译器-10.html

    https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

    https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate


    程序设计语言的存储分配策略

    C中的内存—堆栈、堆和静态

    enter image description hereMEMORY IN C – THE STACK, THE HEAP, AND STATIC

  • OP:操作系统或语言运行库在多大程度上控制它们?
  • 关于这个重要问题,我想补充几点:

    操作系统和公共语言运行时

    .NET框架的关键组件enter image description hereNET框架4.5体系结构enter image description here

    CLR的组件Components of CLRenter image description hereenter image description hereenter image description here