栈和堆的区别

2025年4月19日 | 阅读 9 分钟

在理解堆栈和堆数据结构之间的区别之前,我们应该先分别了解堆栈和堆数据结构。

什么是堆栈?

堆栈是一种用于组织数据的数据结构。堆栈类似于现实世界中组织物体的方式。现实世界中堆栈的一些例子包括一叠餐盘、一个称为“汉诺塔”的数学谜题,其中包含三个带有多个圆盘的杆,以及一叠网球。堆栈是一个集合,其特性是一个项目或对象必须从一端移除,称为“堆栈顶部”。

它不能被视为一种属性,而应被视为应用于堆栈的约束或限制。换句话说,我们可以说只有堆栈顶部是可访问的,任何项目都可以从堆栈顶部移除或插入。它遵循 LIFO(后进先出)原则,其中最近添加的元素必须首先从堆栈中移除。

堆栈是一个列表或集合,其插入和删除操作都发生在称为堆栈顶部的一端。

操作

以下是堆栈 ADT 可用的两个操作:

  • Push(入栈):将元素插入堆栈的过程称为入栈操作。入栈操作可以写成:
    Push(x):将元素 x 插入堆栈。
  • Pop(出栈):从堆栈中删除元素的过程称为出栈操作。出栈操作可以表示为:
    Pop():从堆栈中移除最近添加的元素。

堆栈的表示

堆栈如下图所示:

Stack vs Heap

从上图可以看出,堆栈看起来像一个从一侧打开的容器。它可以在逻辑上表示为一个从一侧打开的容器的三侧图形。上图表示一个空的堆栈,我们假设堆栈为“s”。这是一个整数堆栈。现在我们将对堆栈执行入栈和出栈操作。假设我们要将 2 插入堆栈。入栈操作后,堆栈将显示为:

Stack vs Heap

由于堆栈中只有一个元素,因此元素 2 将位于堆栈顶部。如果要将 1 插入堆栈,堆栈将显示为:

Stack vs Heap

由于元素 1 位于元素 2 的上方,因此元素 1 将被视为堆栈顶部。如果执行出栈操作,则如上所示,最顶部的元素,即 1,将被从堆栈中移除:

Stack vs Heap

什么是堆?

也是一种数据结构或用于存储全局变量的内存。默认情况下,所有全局变量都存储在堆内存中。它允许动态内存分配。堆内存不由 CPU 管理。堆数据结构可以使用数组或树来实现。

它是一个完全二叉树,满足堆属性条件,其中完全二叉树是所有层都已完全填充,只有最后一层除外。在最后一层,所有节点都尽可能靠左。

指针和动态内存

在这里,我们将研究内存的架构。了解系统如何管理内存以及我们程序员如何访问内存至关重要。在典型的架构中,分配给程序或应用程序的内存分为四个段。内存的一个段存储要执行的指令。内存的另一个段存储全局变量或静态变量。全局变量是在函数外部声明的变量,其生命周期贯穿整个程序。第三个段,即堆栈,用于存储所有函数调用和局部变量。当调用任何函数时,它会在内存中占用一些空间,称为堆栈内存。

Stack vs Heap

让我们通过一个例子来理解堆栈内存。

在上面的代码中,执行从 main() 方法开始。因此,main() 方法将在堆栈中获得内存,如下所示:

Stack vs Heap

当调用 sum() 方法时,控制将转移到 sum() 函数。

Stack vs Heap

sum() 方法调用 square() 方法;因此,square() 方法将在堆栈中获得内存,如下所示:

Stack vs Heap

一旦 square() 方法的 return 语句被执行,控制将返回到 sum() 方法,并且 square() 方法将从堆栈中移除,如下所示:

Stack vs Heap

当 sum() 方法的 return 语句被执行时,控制将返回到 main() 方法,并且 sum() 方法将从堆栈中移除,如下所示:

Stack vs Heap

在 main() 方法中,调用了 printf() 函数,因此它将在堆栈中获得内存,如下所示:

Stack vs Heap

一旦 printf() 语句的执行完成,printf() 和 main() 方法将从堆栈内存中移除,如下所示:

Stack vs Heap

使用堆栈内存存在一些限制。假设操作系统为程序保留了 1MB 的堆栈内存。如果程序不断调用函数,堆栈内存将不足,并导致堆栈溢出。堆栈溢出会导致程序崩溃。因此,我们可以说堆栈内存不会在运行时增长。

堆栈的另一个限制是变量的作用域无法操纵。堆栈上内存的分配和释放由规则设定,即当调用函数时,它会被推入堆栈顶部;当调用 pop() 操作时,元素将从堆栈顶部移除。

堆栈的第三个限制是,如果我们定义大型数据类型(如数组),并且数组的大小未在编译时定义。我们想根据某些参数定义数组的大小,而堆栈无法在运行时定义数组的大小。

因此,为了分配大块内存并将内存保留一段时间,我们可以使用 数据结构。与堆栈数据结构不同,堆内存的大小可以变化,并且在应用程序的整个生命周期中不是固定的。在堆内存中,对于内存的分配和释放没有固定的规则。程序员可以自己手动处理内存。对程序员来说,将堆视为可供使用的免费大片内存,我们可以根据需要使用它。

堆也称为动态内存,使用堆可以被视为动态内存分配。要使用动态内存,我们需要使用一些函数。在 C 语言 中,我们可以使用 malloc() 和 calloc() 来分配内存,并使用 free() 函数来释放内存;而在 C++ 语言 中,我们使用 new 运算符进行分配,使用 delete 运算符进行释放。

让我们通过一个例子来理解动态内存。

上述代码的解释。

首先,我们声明了变量 'a',它在 main() 方法的堆栈帧内分配在堆栈中,如下所示:

Stack vs Heap

要分配到 堆内存,我们需要使用 malloc() 函数。我们在上述代码中使用了 malloc() 函数,其中传递了 sizeof(int),表示在堆内存中分配了 4 字节的块。该函数返回一个 void 指针,其中包含块的起始地址。'p' 是函数局部的一个指针变量,因此它存储在堆栈中,如下所示:

Stack vs Heap

再次,我们分配了由 'p' 变量指向的新内存块,因此 'p' 不持有先前块的地址。具有值 11 的块是内存的不必要消耗。在堆中,内存不会自动释放,我们必须手动释放内存。因此,在上述代码中,我们使用了 free(p) 函数,其中我们将 'p' 传递给释放 'p' 指向的内存。

堆栈与堆的区别

Stack vs Heap
Stack
堆栈提供静态内存分配,即用于存储临时变量。堆提供动态内存分配。默认情况下,所有全局变量都存储在堆中。
它是一种线性数据结构,意味着数据按线性方式存储,即一个数据后面跟着一个数据。它是一种分层数据结构,意味着数据以树的形式存储。
它用于访问局部变量。默认情况下,它用于访问全局变量。
堆栈内存的大小是有限的,取决于操作系统。内存的大小没有限制。
由于它是一种线性数据结构,因此数据存储在连续的块中。由于它是一种分层数据结构,因此元素以随机方式存储。
在堆栈中,分配和释放是自动管理的。在堆中,内存是手动管理的。
堆栈可以使用数组、链表和动态内存三种形式实现。堆可以使用数组和树两种形式实现。
使用堆栈的主要问题是内存不足,因为运行时无法更改内存大小。堆栈的大小在编译时确定。使用堆的主要问题是内存碎片。这里,内存碎片意味着内存被浪费了。
它的大小是固定的。它易于使用,因为堆的大小可以根据我们的需求而变化。
堆栈的访问时间更快。堆的访问时间更慢。
堆栈内存的大小由操作系统决定。堆内存的大小由程序员决定。
变量的作用域无法更改。变量的作用域可以更改。
堆栈是一种提供静态内存分配的数据结构。它遵循后进先出(LIFO)原则,内存按此顺序分配和释放。堆栈通常用于存储临时变量和与函数调用相关的信息。动态内存分配是一项有用的功能,它允许程序员根据需要分配和释放内存。这有助于存储全局变量和动态分配的内存,这些内存的大小可以在程序执行期间更改。内存会根据需要分配和释放,从而提供灵活性并高效利用系统资源。
局部存储的变量和函数调用详细信息是此工具的主要用例。它帮助您有效访问和管理这些元素。内存分配是编程中的一项关键功能。它用于根据需要动态地为变量和其他数据结构分配内存空间。
调用堆栈是一种跟踪程序中正在调用的函数的数据结构。它存储有关每个函数调用的信息,例如局部变量以及函数被执行的顺序。动态分配内存是编程中的一项关键功能。它允许创建大小未知或需要在其创建函数之外持续存在的数据结构。这种灵活性对于构建能够适应不断变化的需求和处理不同量数据的复杂应用程序至关重要。
堆栈上的内存分配和释放遵循清晰的模式,因此不存在碎片问题。堆栈以结构化的方式管理内存,从而避免了碎片可能引起的问题。随着内存部分的使用和释放,内存碎片可能会随着时间的推移而发生。这可能导致内存使用效率低下,并可能影响性能。