点击蓝色“最码农”关注我哟
加个“星标”,每天下午18:03,一起学技术
对比理解堆栈与堆的结构!
内存分配
全局段(Global segment) 代码段(Code segment) 堆栈(Stack) 堆(Heap)
全局段,负责存储全局变量和静态变量,这些变量的生命周期等于程序执行的整个持续时间。
代码段,也称为文本段,包含组成我们程序的实际机器代码或指令,包括函数和方法。
堆栈段,用于管理局部变量、函数参数和控制信息(例如返回地址)。
堆段,提供了一个灵活的区域来存储大型数据结构和具有动态生命周期的对象。堆内存可以在程序执行期间分配或释放。
注意:值得注意的是,内存分配上下文中的堆栈和堆不应与数据结构堆栈和堆混淆,它们具有不同的用途和功能。
程序代码的大小。
全局变量的数量和大小。
程序所需的动态内存分配量。
程序使用的调用堆栈的大小。
public class Main {
// Global Segment:全局变量存放在这里
static int globalVar = 42;
// 代码段:这里存放函数和方法
public static int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
// 代码段:调用add函数
int sum = add(globalVar, 10);
System.out.println('Sum: ' + sum);
}
}
globalVar在这些代码示例中,我们有一个值为 的全局变量42,它存储在全局段中。我们还有一个函数add,它接受两个整数参数并返回它们sum;该函数存储在代码段中。该main函数(或 Python 中的脚本)调用该add函数,传递全局变量和另一个整数值10作为参数。
代码中的全局和代码段(未显示堆和堆栈段)
需要强调的是,管理堆栈和堆段对于代码的性能和效率起着重要作用,使其成为编程的一个重要方面。因此,程序员在深入研究它们的差异之前应该充分理解它们。
栈内存:有序存储
下面不同编程语言的代码实例演示了堆栈在各种情况下的使用。
public class StackExample {
// 一个简单的函数来添加两个数字
public static int add(int a, int b) {
// 局部变量(存储在栈中)
int sum = a + b;
return sum;
}
public static void main(String[] args) {
// 局部变量(存储在栈中)
int x = 5;
// 函数调用(存储在堆栈中)
int result = add(x, 10);
System.out.println('Result: ' + result);
}
}
调用函数时会创建称为堆栈帧的内存块。堆栈帧存储与局部变量、参数和函数的返回地址相关的信息。该内存是在堆栈段上创建的。
堆栈段为空
为主函数创建一个新的堆栈帧
在 main 函数的堆栈帧中,局部变量 x 现在的值为 5
调用 add 函数,实际参数为 (5, 10)
控制权转移到 add 函数,为 add 函数创建一个新的堆栈帧,其中包含局部变量 a、b 和 sum
add 函数的堆栈帧上的 sum 变量被分配 a + b 的结果
add 函数完成其任务并且其堆栈帧被销毁
具有可变结果的主函数的堆栈帧存储从 add 函数返回的值
在显示结果值(此处未显示)后,主功能块也被销毁,并且堆栈段再次为空
以下是 C++ 代码按执行顺序的解释:
第 10 行:程序从该main函数开始,并为其创建一个新的堆栈帧。 第 12 行:局部变量x被赋值为5。 第 15 行:add使用参数x和调用该函数10。 第 4 行:为该函数创建一个新的堆栈帧add。控制权转移到add带有局部变量的函数。a、b、 和sum。变量a和分别被赋予和b的值。x10 第 6 行:局部变量sum被赋值为a + b(即 5 + 10)。 第 7 行:变量sum的值(即 15)被返回给调用者。 第 8 行:add从堆栈中弹出函数的堆栈帧,并释放所有局部变量(、和a) b。sum 第15行:result函数堆栈帧上的局部变量main被赋予返回值(即15)。 第 17 行:存储在变量中的值result(即 15)使用 打印到控制台std::cout。 第 19 行:函数main返回 0,表示执行成功。 第 20 行:函数main的堆栈帧从堆栈中弹出,并且所有局部变量 (x和result) 都被释放。
堆栈存储器的主要特点
固定大小:当涉及到堆栈内存时,其大小保持固定,并在程序执行开始时确定。 速度优势:堆栈内存帧是连续的。因此,在堆栈内存中分配和释放内存的速度非常快。这是通过操作系统管理的堆栈指针对引用进行简单调整来完成的。 控制信息和变量的存储:堆栈内存负责容纳控制信息、局部变量和函数参数,包括返回地址。 有限的可访问性:请务必记住,存储在堆栈内存中的数据只能在活动函数调用期间访问。 自动管理:堆栈内存的高效管理由系统本身完成,不需要我们额外的工作。
堆内存:动态存储
下面不同编程语言的代码实例演示了堆的使用。
public class HeapExample {
public static void main(String[] args) {
// 栈:局部变量“value”存储在 栈中
int value = 42;
// 堆:为堆上的单个 Integer 分配内存
Integer ptr = new Integer(value);
// 将值分配给分配的内存并打印它
System.out.println('Value: ' + ptr);
// 在Java中,垃圾收集是自动的,因此不需要 释放内存
}
}
演示 Java 中的堆内存分配和使用
int* ptr在C++中。 Java 中的一个Integer对象ptr。 ptrPython 中包含单个元素的列表。
注意:在 Java 和 Python 中,垃圾收集会自动处理内存释放,无需手动释放内存,如 C++ 中所示。
栈段和堆段为空
为主函数创建一个新的堆栈帧
局部变量值被赋予值 42
在堆上分配了一个指针变量ptr,指针ptr中存放的是分配的堆内存的地址(即0x1000)
value变量中存储的值(即42)被赋值给ptr指向的内存位置(堆地址0x1000)
堆上地址 0x1000 处分配的内存被释放
main函数的栈帧从栈中弹出(显示result的值后),栈段和堆段再次清空
以下是 C++ 代码按执行顺序的解释:
第 3 行:main调用该函数,并为其创建一个新的堆栈帧。 第 5 行:堆栈帧上的局部变量value被赋值为42。 第 8 行:ptr使用关键字为堆上的单个整数动态创建的内存分配给指针变量new。我们假设堆上新内存的地址为 0x1000。分配的堆内存的地址(0x1000)存储在指针中。ptr。 第 11 行:将整数值42分配给ptr(堆地址 0x1000)所指向的内存位置。 第 12 行:(ptr )指向的内存位置存储的值42被打印到控制台。 第 15 行:使用关键字释放在堆上地址 0x1000 处分配的内存delete。在此行之后,ptr成为悬空指针,因为它仍然保存地址 0x1000,但该内存已被释放。然而,对于这个重要的讨论,我们不会详细讨论悬空指针。 第17行: main函数返回0,表示执行成功。 第 18 行:从堆栈中弹出主函数的堆栈帧,并释放所有局部变量 (value和)。ptr
注意:C++ 标准库还提供了一系列智能指针,可以帮助自动化堆中内存分配和释放的过程。
堆内存的主要特点
以下是需要记住的堆内存的一些显着特征:
大小的灵活性:堆内存大小可以在程序执行过程中发生变化。 速度权衡:在堆中分配和释放内存速度较慢,因为它涉及寻找合适的内存帧和处理碎片。 动态对象的存储:堆内存存储具有动态生命周期的对象和数据结构,如newJava 或 C++ 中使用关键字创建的对象和数据结构。 持久数据:存储在堆内存中的数据将一直保留在那里,直到我们手动释放它或程序结束。 手动管理:在某些编程语言(例如C和C++)中,必须手动管理堆内存。如果处理不当,可能会导致内存泄漏或资源使用效率低下。
堆栈与堆:差异对比
现在我们彻底了解了堆栈和堆内存分配的工作原理,我们可以区分它们了。在比较栈内存和堆内存时,我们必须考虑它们的独特特性来理解它们的差异:
大小管理:堆栈内存具有在程序执行开始时确定的固定大小,而堆内存是灵活的,可以在程序的整个生命周期中更改。 速度:堆栈内存在分配和释放内存时具有速度优势,因为它只需要调整引用。相反,由于需要定位合适的内存帧并管理碎片,堆内存操作速度较慢。 存储目的:堆栈内存指定用于控制信息(例如函数调用和返回地址)、局部变量和函数参数(包括返回地址)。另一方面,堆内存用于存储具有动态生命周期的对象和数据结构,例如newJava 或 C++ 中使用关键字创建的对象和数据结构。 数据可访问性:堆栈内存中的数据只能在活动函数调用期间访问,而堆内存中的数据在手动释放或程序结束之前仍然可以访问。 内存管理:系统自动管理堆栈内存,优化其使用,以实现快速高效的内存引用。相比之下,堆内存管理是程序员的责任,处理不当可能会导致内存泄漏或资源使用效率低下。
下表总结了堆栈内存和堆内存在不同方面的主要区别:
方面对比 | 堆栈内存 | 堆内存 |
尺寸管理 | 固定大小,在程序开始时确定 | 灵活的大小,可以在程序的生命周期中改变 |
速度 | 更快,只需要调整一个参考 | 速度较慢,涉及定位合适的块和管理碎片 |
储存目的 | 控制信息、局部变量、函数参数 | 具有动态生命周期的对象和数据结构 |
数据可访问性 | 仅在活动函数调用期间可访问 | 在手动释放或程序结束之前均可访问 |
内存管理 | 由系统自动管理 | 由程序员手动管理 |
堆栈内存与堆内存:何时使用每种类型
当需要存储对象、数据结构或动态分配的数组时,其生命周期在编译时或函数调用期间无法预测。
当内存需求很大或者我们需要在程序的不同部分之间共享数据时。
当需要分配超出单个函数调用范围的内存时。