最近在学习计算机底层相关的知识,看到内存这块内容时有个疑问,为什么要提出堆、栈的概念?当初是为了解决什么问题呢?除了堆栈外内存还存在其他分区吗?大学里学过微机原理涉及到一些相关内容但是到如今已经忘得差不多了。还是重新找资料记录一下学习过程吧!
堆、栈的提出
计算机在最开始的时候,既没有堆也没有栈。当下图中的冯·诺依曼架构提出的时候,只是说计算机要有“存储器”。
所以,早期的计算机整个内存空间都是由用户任意使用,不存在什么分区,其中的每个bit都属于全局变量。这样的话会导致错误的操作,破坏系统的稳定性和安全性,同时内存管理也会变得很复杂性能也会降低。
在二十世纪六十年代,一些计算机科学家提出了ALGOL 58语言,代码示例如下:
procedure Absmax(a) Size:(n, m) Result:(y) Subscripts:(i, k);
value n, m; array a; integer n, m, i, k; real y;
comment The absolute greatest element of the matrix a, of size n by m,
is copied to y, and the subscripts of this element to i and k;
begin
integer p, q;
y := 0; i := k := 1;
for p := 1 step 1 until n do
for q := 1 step 1 until m do
if abs(a[p, q]) > y then
begin y := abs(a[p, q]);
i := p; k := q
end
end Absmax
ALGOL语言提出了结构化编程的理念,将代码划分成块,比如if块、循环块、函数块,每块用begin...end包起来。在每块里面存在的局部变量也要本地化,不能再用全局变量,避免变量耦合。
那么,现在就要思考一个问题了 ,如何实现变量的局部化呢?从内存的角度考虑就是在进入每个块的时候为这个块新开辟一块内存,这块内存只属于这个块。有一点需要注意的是块之间是可以嵌套的,A可以调用B,B可以调用C,而且这个嵌套结构有个特点:程序执行时,最后执行的块会最先执行完,所以后申请的块内存会先释放,这属于是后进先出的数据结构。
上面就是栈的特点,遇到变量先放进内存,退出块时再取出来,Last-In-First-Out数据结构。并不是科学家发明了“栈”这个概念,应用到了计算机语言中,而是在实现结构化编程时,发现必须使用一种LIFO的数据结构,这个结构和货栈堆货是一个道理,先放的货在下边,后放的货在上边,取的时候先取上边后放的,再取下边先放的,既然这样,那这个LIFO结构干脆就叫“栈(Stack)”吧。
在二十世纪七十年代,结构化编程思想被大家接受,这就造成了对内存的放取操作。为了提高效率,DEC在推出的PDP-6计算机中,率先提供了PUSH、POP、PUSHJ、POPJ四个CPU指令。
”堆“这个概念是在BCPL语言中最先提出来的,提供了GETBLK
(n)和FREEBLK
(p)两个函数。这两个函数可以在运行时动态申请一块内存。终于不用在代码里写死数据体大小了!BCPL语言发展到C语言,就是著名的malloc和free函数。
这就是“堆”,为了能让你推迟到运行时再决定一块数据的大小,而不是在写代码时就必须决定。注意这一点和“栈”是不同的,绝大多数语言栈上数据的大小必须在编译期决定。
堆区、栈区之间的区别
一、管理方式不同
-
栈区:
- 自动管理:栈区的内存管理通常是自动进行的。当函数被调用时,系统自动为函数的局部变量和参数分配栈空间;当函数返回时,这些空间会自动被释放。这种自动管理方式使得栈的使用非常方便,不需要程序员显式地进行内存分配和释放操作,降低了编程的复杂性和出错的可能性。
- 快速分配和释放:由于栈的管理是基于简单的后进先出(LIFO)原则,内存的分配和释放非常迅速。这对于频繁调用的函数和需要快速执行的代码非常重要,可以提高程序的执行效率。
-
堆区:
- 手动管理:堆区的内存管理由程序员手动控制。程序员可以在运行时根据需要动态地分配和释放内存,使用诸如
malloc
、new
和free
、delete
等函数。这种手动管理方式虽然更加灵活,但也增加了编程的复杂性,需要程序员确保正确地分配和释放内存,以避免内存泄漏和悬空指针等问题。 - 灵活的大小和生命周期:堆区的内存分配更加灵活,可以分配任意大小的内存块,并且内存块的生命周期可以由程序员控制。这对于需要在程序运行过程中动态调整内存使用的情况非常有用,例如处理动态数据结构(如链表、树等)或需要长时间存储数据的情况。
- 手动管理:堆区的内存管理由程序员手动控制。程序员可以在运行时根据需要动态地分配和释放内存,使用诸如
二、内存使用特点不同
-
栈区:
- 大小有限:栈的大小通常是有限的,一般在几兆字节到几十兆字节之间。这是因为栈的空间是在程序启动时预先分配的,并且栈的增长是向下的(朝着低地址方向),如果栈空间用尽,会导致栈溢出错误。
- 局部性好:由于栈是用于存储函数调用的上下文信息,函数的局部变量和参数通常在短时间内被频繁访问,具有较好的局部性。这种局部性有利于提高 CPU 的缓存命中率,从而提高程序的执行效率。
-
堆区:
- 大小相对较大:堆的大小通常比栈大得多,可以根据系统的可用内存进行动态调整。这使得堆区可以满足程序对大量内存的需求,特别是在处理大型数据结构或长时间运行的程序中。
- 分配和释放不连续:堆区的内存分配和释放是不连续的,不同的内存块可能在不同的时间被分配和释放,导致内存空间的碎片化。这可能会影响内存的分配效率,特别是当需要分配较大的连续内存块时。
三、数据存储需求不同
-
栈区:
- 存储临时数据:栈区主要用于存储函数调用过程中的临时数据,如局部变量、函数参数和返回地址等。这些数据的生命周期通常与函数的执行时间相关,函数返回后,这些数据就不再需要了。
- 快速访问:由于栈的管理方式简单,栈区的数据访问速度通常很快。这对于需要频繁访问的局部变量和参数非常重要,可以提高程序的执行效率。
-
堆区:
- 存储动态数据结构:堆区主要用于存储动态分配的数据结构,如链表、树、动态数组等。这些数据结构的大小和生命周期在程序运行过程中可能会发生变化,需要动态地分配和释放内存。
- 长期存储数据:堆区还可以用于存储需要在程序运行过程中长时间保存的数据,例如在文件读取、网络通信等场景中,需要将数据存储在堆区以便在不同的函数之间传递和处理。