什么是堆栈和堆?

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

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

答案

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

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

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

要直接回答您的问题:

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

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

他们的范围是什么?

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

什么决定了它们的大小?

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

是什么使速度更快?

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

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

堆:

  • 就像堆一样存储在计算机 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;

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

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

    像纸堆一样堆

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

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

    像一堆甘草一样堆

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

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

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

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

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

  • 他们的范围是什么?

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

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

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

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

  • 是什么使速度更快?

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

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

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

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

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

堆

堆栈

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

堆栈

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

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

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

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

在下面的 C#代码中

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

这是管理内存的方式

堆栈上变量的图片

仅需要持续函数调用的Local Variables就进入堆栈。堆用于变量,这些变量的寿命我们并不是很早就知道,但是我们希望它们能持续一段时间。在大多数语言中,至关重要的一点是,我们要在编译时知道要将变量存储在堆栈中的大小。

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

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

更多信息可以在这里找到:

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

和这里:

在堆栈和堆上创建对象

本文是上述图片的来源: 六个重要的. NET 概念:堆栈,堆,值类型,引用类型,装箱和拆箱 - CodeProject

但请注意,其中可能包含一些错误。

堆栈调用函数时,该函数的参数以及其他一些开销会放在堆栈上。一些信息(例如返回目的地)也存储在此处。当您在函数内声明变量时,该变量也分配在堆栈上。

取消分配堆栈非常简单,因为您总是以相反的顺序进行分配。输入函数时会添加堆栈内容,退出它们时会删除相应的数据。这意味着除非您调用许多调用许多其他函数的函数(或创建递归解决方案),否则您往往会停留在堆栈的较小区域内。

该堆堆是你把你快速创建数据,其中的通用名称。如果您不知道程序将要创建多少个太空飞船,则很可能使用新的(或 malloc 或等效的)运算符来创建每个太空飞船。这种分配将持续一段时间,因此很可能我们将以与创建它们不同的顺序释放事物。

因此,堆要复杂得多,因为最终会出现一些未使用的内存区域与块相互交错的情况 - 内存碎片化了。寻找所需大小的可用内存是一个难题。这就是为什么应该避免使用堆的原因(尽管仍然经常使用堆)。

实现堆栈和堆的实现通常取决于运行时 / OS。通常,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,该解决方案从堆中获取大量内存,然后在内部进行分配,以避免依赖 OS 来获取内存。

仅当您的内存使用量与常规使用情况大不相同时(例如,对于您在一个大型操作中加载一个关卡并可以在另一个大型操作中将所有内容丢掉的游戏),这才是实用的。

内存中的物理位置这与您想像的无关紧要,因为一项称为 “ 虚拟内存 ” 的技术使您的程序认为您可以访问物理数据在其他地方(甚至在硬盘上!)的某个地址。随着调用树的不断深入,您为堆栈获取的地址按顺序递增。堆的地址是不可预测的(即特定于隐含的),并且坦率地说并不重要。

为了澄清, 此答案的信息不正确( 托马斯在评论后将其答案固定,很酷:))。其他答案只是避免解释静态分配的含义。因此,我将在下面解释三种主要的分配形式,以及它们通常与堆,堆栈和数据段的关系。我还将在 C / C ++ 和 Python 中显示一些示例,以帮助人们理解。

“静态”(又名静态分配)变量未分配在堆栈上。不要这么假设 - 许多人这样做只是因为 “静态” 听起来很像 “堆栈”。它们实际上既不存在于堆栈中,也不存在于堆中。是所谓的数据段的一部分

但是,通常最好考虑 “ 作用域 ” 和 “ 生存期 ”,而不是 “堆栈” 和 “堆”。

范围是指代码的哪些部分可以访问变量。通常,我们认为本地范围 (只能由当前函数访问)与全局范围 (可以在任何地方访问)相比,尽管范围会变得更加复杂。

生存期是指在程序执行期间何时分配和释放变量。通常,我们认为静态分配 (变量将在程序的整个过程中持续存在,从而使它在多个函数调用之间存储相同的信息非常有用)与自动分配 (变量仅在单个函数调用中持续存在,从而对于以下情况有用)存储仅在函数运行期间使用的信息,完成后可以丢弃)与动态分配 (持续时间在运行时定义的变量,而不是像静态或自动那样的编译时间)进行存储。

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

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

// 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 ++ 中的某些语法选择加剧了这个问题 - 例如,由于以下所示的语法,许多人认为全局变量不是 “静态” 的。

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

请注意,在上面的声明中添加关键字 “static” 可防止 var2 具有全局作用域。但是,全局 var1 具有静态分配。这不直观!因此,在描述范围时,我尽量不要使用 “静态” 一词,而应使用诸如 “文件” 或 “文件受限” 范围之类的名称。但是,许多人使用短语 “静态” 或 “静态作用域” 来描述只能从一个代码文件访问的变量。在生命周期的上下文中,“静态” 始终表示变量在程序启动时分配,并在程序退出时释放。

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

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

其他人已经很好地回答了大笔画,因此我将介绍一些细节。

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

  2. 在 C 语言中,您可以通过使用alloca来获得可变长度分配的好处, alloca可以在堆栈上进行分配,而 alloc 可以在堆上进行分配。此内存将无法在 return 语句中保留下来,但是对于暂存缓冲区很有用。

  3. 在 Windows 上使用不多的巨大临时缓冲区并不是免费的。这是因为编译器将生成一个堆栈探测循环,每次进入您的函数时都会调用该循环,以确保堆栈存在(因为 Windows 在堆栈末尾使用单个保护页来检测何时需要增大堆栈。如果您访问的内存超出了堆栈末尾的一页,则会崩溃。例:

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

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

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

在没有虚拟内存的系统(例如某些嵌入式系统)中,通常使用相同的基本布局,只是堆栈和堆的大小固定不变。但是,在其他嵌入式系统(例如基于 Microchip PIC 微控制器的嵌入式系统)中,程序堆栈是单独的存储器块,数据移动指令无法对其进行寻址,并且只能通过程序流指令(调用,返回等)。其他体系结构(例如 Intel Itanium 处理器)具有多个堆栈 。从这个意义上讲,堆栈是 CPU 体系结构的元素。

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

堆是操作系统通常通过诸如 malloc 之类的 syscall 分配给应用程序的一部分内存。在现代 OS 上,此内存是仅呼叫进程有权访问的一组页面。

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

因为在启动程序之前已经分配了堆栈,所以您不需要使用 malloc 就可以使用堆栈,因此那是一个小优点。在实践中,很难预测在具有虚拟内存子系统的现代操作系统中哪些将是快的,哪些将是慢的,因为页面的实现方式和存储位置是实现细节。