到底为什么需要堆和栈?使用场景是什么?底层原理是什么?
在计算机编程中,堆和栈是两种重要的内存区域,它们有着不同的特点和用途。
为什么需要堆和栈
栈:主要用于存储函数调用时的局部变量、函数参数、返回地址等信息。它的特点是先进后出(FILO),就像一摞盘子,最后放上去的盘子最先被拿走。这种数据结构能高效地管理函数的执行过程,保证函数调用的正确顺序和局部变量的正确访问,让程序能按照预定的逻辑依次执行各个函数。堆:用于动态分配内存,当程序运行时需要在运行期间申请不确定大小的内存空间时,就会使用堆。比如,创建一个大小在编译时无法确定的数组,或者动态创建一个对象,这些都需要在堆上分配内存。堆的存在使得程序能够根据实际运行情况灵活地管理内存,满足不同数据结构和对象的内存需求。
使用场景
栈
函数调用:在函数调用过程中,栈用于保存函数的上下文信息,包括局部变量、参数和返回地址。当函数被调用时,这些信息被压入栈中;函数执行完毕后,这些信息从栈中弹出,程序回到调用函数的位置继续执行。表达式求值:在计算表达式时,栈可以用来存储操作数和中间结果。例如,对于表达式(3 + 5) * 2,在计算过程中,操作数3和5会先被压入栈,然后计算3 + 5,结果8再被压入栈,接着2入栈,最后计算8 * 2,并将最终结果弹出栈。
堆
动态内存分配:当需要创建动态大小的数据结构,如动态数组、链表、树等数据结构时,需要在堆上分配内存。因为这些数据结构的大小在编译时是不确定的,只有在程序运行时才能根据实际情况确定所需内存的大小。对象创建:在面向对象编程中,对象通常在堆上创建。例如,使用new关键字在Java或C++ 中创建一个对象,就是在堆上分配内存来存储对象的成员变量和方法。这样可以在程序运行时动态地创建和销毁对象,满足不同业务场景下对对象的需求。
底层原理
栈:栈通常是由编译器自动管理的一块连续的内存区域。当程序执行到函数调用时,编译器会为函数的局部变量和参数在栈上分配空间,并将函数的返回地址压入栈中。当函数执行完毕,编译器会自动释放栈上为该函数分配的空间,将栈顶指针恢复到函数调用前的位置,这个过程不需要程序员手动干预。堆:堆是一块相对自由的内存区域,其管理相对复杂。当程序需要在堆上分配内存时,会调用内存分配函数(如C语言中的malloc函数)向操作系统请求一定大小的内存空间。操作系统会在堆中寻找一块足够大的空闲内存块分配给程序,并返回该内存块的首地址。程序使用完这块内存后,需要调用相应的释放函数(如free函数)将内存归还给操作系统,以便其他程序或本程序的其他部分可以再次使用这块内存。如果程序在堆上分配了内存但没有及时释放,就会导致内存泄漏,浪费系统资源。
想象你要举办一场派对,栈就像是派对的接待处。当有客人(函数)来参加派对时,接待处会记录客人的信息(局部变量、参数),并告诉客人当派对结束后从哪里离开(返回地址)。客人按照到达的顺序依次在接待处登记,最后来的客人会排在最前面,先接受服务。当派对结束时,客人按照相反的顺序离开接待处,最后来的客人最先离开。这样,接待处(栈)就能有条不紊地管理客人(函数)的进出,保证派对(程序)的顺利进行。
而堆呢,就像是派对的仓库。你不知道派对上到底需要多少食物、饮料和装饰品,所以你会在派对开始前根据大致的估计从仓库里取出一些物品,但在派对进行过程中,如果发现东西不够了,就需要随时去仓库里再取一些。这些物品的摆放位置并不是固定的,你需要记住从哪里取的,以便在派对结束后把剩余的物品放回原处,让仓库保持整洁,方便下次使用。这就像在堆上动态分配和释放内存一样,根据程序的实际需求在堆上申请和释放内存空间,以满足不同的存储需求。