一尘不染

堆栈和堆是什么

javascript

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

  • 它们在哪里以及是什么(物理上在真实计算机的内存中)?
  • 它们在多大程度上受操作系统或语言运行时的控制?
  • 它们的范围是什么?
  • 是什么决定了它们的大小?
  • 是什么让一个更快?

阅读 209

收藏
2021-12-30

共3个答案

一尘不染

堆:

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

堆:

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

例子:

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;
2021-12-30
一尘不染

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

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

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

直接回答您的问题:

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

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

它们的范围是什么?

堆栈附加到一个线程,因此当线程退出时,堆栈被回收。堆通常在应用程序启动时由运行时分配,并在应用程序(技术进程)退出时回收。

是什么决定了它们的大小?

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

是什么让一个更快?

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

一个清晰的示范: img

2021-12-30
一尘不染

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

  • 在一堆物品中,物品按照它们放置在那里的顺序一个叠放在另一个上面,并且您只能移除顶部的一个(而不能将整个东西翻倒)。

像一摞纸一样叠起来

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

  • 在堆中,项目的放置方式没有特定的顺序。您可以按任何顺序进入和移除项目,因为没有明确的“顶部”项目。

堆得像一堆甘草

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

这些图像应该很好地描述了在堆栈和堆中分配和释放内存的两种方式。好吃!

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

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

堆是用于动态和随机分配的任何内存的通用术语;即乱序。内存通常由操作系统分配,应用程序调用 API 函数来执行此分配。管理动态分配的内存需要相当多的开销,这通常由所使用的编程语言或环境的运行时代码处理。

  • 它们的范围是什么?

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

  • 是什么决定了它们的大小?

同样,它取决于语言、编译器、操作系统和体系结构。堆栈通常是预先分配的,因为根据定义它必须是连续的内存。语言编译器或操作系统决定了它的大小。您不会在堆栈上存储大量数据,因此它会足够大以至于永远不会被完全使用,除非出现不需要的无限递归(因此,“堆栈溢出”)或其他不寻常的编程决策。

堆是任何可以动态分配的东西的总称。根据您查看它的方式,它的大小会不断变化。在现代处理器和操作系统中,它的确切工作方式无论如何都是非常抽象的,所以你通常不需要担心它的深层工作方式,除了(在它允许的语言中)你不能使用内存您尚未分配或已释放的内存。

  • 是什么让一个更快?

堆栈更快,因为所有空闲内存始终是连续的。不需要维护所有空闲内存段的列表,只需一个指向当前堆栈顶部的指针。为此,编译器通常将此指针存储在一个特殊的、快速的寄存器中。更重要的是,堆栈上的后续操作通常集中在非常接近的内存区域中,在非常低的级别上有利于处理器芯片缓存的优化。

2021-12-30