什么是堆和栈?

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

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

===============>>#1 票数:5789 已采纳

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

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

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

要直接回答您的问题:

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

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

他们的范围是什么?

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

什么决定了它们的大小?

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

是什么使速度更快?

堆栈速度更快,因为访问模式使分配和释放内存变得很简单(指针/整数只是增加或减少),而堆的分配或释放则要复杂得多。 同样,堆栈中的每个字节都倾向于被非常频繁地重用,这意味着它倾向于被映射到处理器的高速缓存中,从而使其非常快。 堆的另一个性能损失是,堆(通常是全局资源)通常必须是多线程安全的,即,每个分配和释放都必须(通常)与程序中的“所有”其他堆访问同步。

清晰的演示:
图片来源: vikashazrati.wordpress.com

===============>>#2 票数:2275

堆:

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

堆:

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

例:

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;

===============>>#3 票数:1340

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

  • 在一堆物品中,物品按照放置在另一个物品上的顺序放在另一个物品的顶部,并且您只能删除顶部的物品(不能将整个物品翻倒)。

     

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

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

     

    堆分配需要完整记录分配的内存和未分配的内存,并进行一些开销维护以减少碎片,找到足够大以适合请求的大小的连续内存段,依此类推。 可以随时释放内存以留下可用空间。 有时,内存分配器将执行维护任务,例如通过移动分配的内存来对内存进行碎片整理或垃圾回收-在运行时识别内存不再在作用域中并对其进行分配。

这些映像应该在描述堆栈和堆中分配和释放内存的两种方式方面做得相当好。 好吃

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

    如前所述,堆和堆栈是通用术语,可以通过多种方式实现。 计算机程序通常具有称为调用堆栈的堆栈 ,该堆栈存储与当前功能相关的信息,例如指向从哪个函数调用的指针以及任何局部变量。 由于函数先调用其他函数然后返回,所以堆栈会不断扩大和缩小,以保存来自函数的信息,直到调用堆栈为止。 一个程序实际上并没有对它的运行时控制。 它由编程语言,操作系统甚至系统架构决定。

    堆是用于动态且随机分配的任何内存的通用术语。 即乱序。 内存通常由OS分配,应用程序调用API函数进行此分配。 管理动态分配的内存需要一定的开销,通常由OS处理。

  • 他们的范围是什么?

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

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

    同样,它取决于语言,编译器,操作系统和体系结构。 堆栈通常是预先分配的,因为根据定义,它必须是连续的内存(有关最后一段的更多信息)。 语言编译器或操作系统确定其大小。 您不会在堆栈上存储大量数据,因此它将足够大,以至于永远不要完全使用它,除非发生不必要的无穷递归(因此,“堆栈溢出”)或其他异常的编程决策。

    堆是可以动态分配的任何事物的通用术语。 根据您看待它的方式,它会不断变化的大小。 在现代处理器和操作系统中,它的确切工作方式无论如何都是非常抽象的,因此您通常不必担心它的内在工作方式,除非(在允许它的语言中)您不得使用会您尚未分配或已释放的内存。

  • 是什么使速度更快?

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

===============>>#4 票数:709

(我将此答案从另一个或多或少是这个问题的重复的问题上移开了。)

您问题的答案是特定于实现的,并且可能会因编译器和处理器体系结构而异。 但是,这是一个简化的说明。

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

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

 

堆栈

  • 堆栈通常与CPU上一个名为堆栈指针的特殊寄存器紧密配合使用。 最初,堆栈指针指向堆栈的顶部(堆栈中的最高地址)。
  • CPU具有用于入堆栈并将其从堆栈弹出的特殊指令。 每次推送都会将值存储在堆栈指针的当前位置,并减少堆栈指针。 pop检索堆栈指针所指向的值,然后增加堆栈指针(不要因向堆栈中添加值会减少堆栈指针,而删除值会增加堆栈指针这一事实而感到困惑。请记住,堆栈会增长为底端)。 存储和检索的值是CPU寄存器的值。
  • 调用函数时,CPU使用特殊指令来推送当前指令指针 ,即在堆栈上执行的代码的地址。 然后,CPU通过将指令指针设置为所调用函数的地址来跳转至该函数。 稍后,当函数返回时,旧的指令指针会从堆栈中弹出,并在调用函数后立即在代码处恢复执行。
  • 输入函数后,将减少堆栈指针以在堆栈上为本地(自动)变量分配更多空间。 如果函数具有一个局部32位变量,则在堆栈上预留4个字节。 当函数返回时,将堆栈指针移回以释放分配的区域。
  • 如果函数具有参数,则在调用函数之前将它们压入堆栈。 然后,函数中的代码可以从当前堆栈指针向上浏览堆栈以找到这些值。
  • 嵌套函数调用的工作方式就像一种魅力。 每个新的调用将分配函数参数,局部变量的返回地址和空间,并且这些激活记录可以堆叠用于嵌套调用,并在函数返回时以正确的方式展开。
  • 由于堆栈是有限的内存块,因此您可能会通过调用过多的嵌套函数和/或为局部变量分配过多的空间而导致堆栈溢出 。 通常,用于堆栈的存储区的设置方式是,在堆栈底部(最低地址)以下进行写入操作将触发CPU陷阱或异常。 然后,运行时可以捕获这种异常情况,并将其转换为某种堆栈溢出异常。

 

可以在堆而不是堆栈上分配函数吗?

不可以,功能(即本地或自动变量)的激活记录分配在堆栈上,不仅用于存储这些变量,而且还用于跟踪嵌套的函数调用。

堆的管理方式实际上取决于运行时环境。 C使用malloc ,C ++使用new ,但是许多其他语言具有垃圾回收。

但是,堆栈是与处理器体系结构紧密相关的更底层的功能。 在没有足够空间的情况下增长堆并不难,因为可以在处理堆的库调用中实现堆。 但是,堆栈堆栈的增长通常是不可能的,因为只有在为时已晚时才发现堆栈溢出。 并关闭执行线程是唯一可行的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值