没想到,评论区的一个提问,成了我知识的裂缝。🤣
前段时间解决了自己代码中的一个关于栈内存问题,便班门弄斧的写了一篇文章栈内存文章,没想到发出来很快便被大佬发现了其中一个问题🤣,在评论区里和大佬开始了你问我答环节,直到最后大佬问了我一个问题,"什么叫C语言栈的自动销毁?何来自动?何为销毁",我才发现自己学习的太浅薄了,便想搞清楚其中的原理🧐。
1. 什么是堆栈?
堆栈是一种特殊的数据结构,它遵循先进后出(LIFO,last in first out)原则,这意味着最后放入的堆栈的元素会最先被取出,堆栈用于多种场景,如函数调用,算法实现等。
1.1. 让我们用一个生活中的例子来理解堆栈:
想象你有一叠盘子。当你清洗盘子并将它们堆放起来时,你通常会将洗净的盘子放在最上面,然后,当你需要使用盘子的时候,你会从最上面的那个,即最后放上去的那个,这就是堆栈的工作方式:最后放上去的盘子总是第一个被使用,而最后放上去的盘子则留在堆栈的底部。
在编程中,堆栈的一个常见用途是存储函数调用时的局部变量和返回地址。当第一个函数被调用时,它的信息(如参数和局部变量)会被"压入"堆栈。当函数执行完毕并返回时,这些信息会被"弹出"堆栈,以便下一个函数调用可以使用堆栈空间。
2. 如何自动的,如何销毁退栈的内存空间?
"自动 "意味着这一过程不需要程序员的干预,它是在函数返回时由系统自行完成的。当一个函数被调用时,它的局部变量会被推入栈中,这些变量只在函数执行期间存在。一旦函数执行完毕并返回,这些局部变量就不再需要了,因此它们占用的内存会被清理,这就是所谓的"销毁"。
在计算机操作系统的底层,程序的内存空间管理是一个自动化的过程。当一个函数调用发生时,操作系统会为该函数的局部变量和其他相关信息在栈上分配内存。这些信息包括函数的参数、返回地址以及局部变量,这个过程称为"入栈"。
当函数执行完毕后,需要将这些局部变量和信息"出栈",即释放这部分内存,这是通过如下步骤实现的:
- 函数返回:当函数完成其任务后,它会准备返回到调用它的地方。这时,CPU会读取之前保存的返回地址,并将控制权交回给调用者。
- 栈指针移动:栈指针(通常是CPU中的一个寄存器)会向上移动,跳过这个函数为局部变量分配的内存区域。这个动作实际上"丢弃"了这些局部变量。
- 内存释放:虽然栈指针移动了,但实际上内存并没有被清理或擦除。它知识被标记为可用,等待下一次函数调用时重写。
- 操作系统的角色:在整个过程中,操作系统负载管理栈的大小和限制。如果一个程序尝试使用超出其分配的栈空间,操作系统会介入并可能终止该程序,这通常称为栈溢出。
这个过程是由编译器和操作系统自动管理的,不需要程序员进行任何手动干预。这就是为什么我们说栈内存是自动分配和销毁的。在程序结束后,操作系统会回收所有内存,包括堆和栈空间。这意味着即使程序员没有显式的释放堆上的内存,操作系统在程序终止时也会确保这些资源得到清理。
3. C语言退出函数栈的时候,自动销毁的底层的原理是什么?
C语言函数栈帧是函数调用过程中在内存的栈区上开辟的一块空间,用来存放函数的参数、返回值、局部变量和上下文信息。函数栈帧由两个寄存器esp和ebp维护,esp指向栈顶,ebp指向栈底。当一个函数被调用时,会发生以下步骤:
- 栈帧的创建:
- push ebp:将当前函数的基指针(ebp)压入栈中,保存上一个栈帧的基指针。
- move ebp, esp:将栈顶指针(esp)的值复制到基指针(ebp),这样ebp就执行当前栈帧的开始位置。
- sub esp, size:通过减少esp的值,为局部变量分配空间。
- 函数执行:函数的局部变量和参数在栈帧中被访问和修改。
- 栈帧的销毁:
- move esp, ebp:在函数结束前,将ebp的值复制回esp,这样esp指向了当前栈帧的开始位置,即销毁了对局部变量的分配。
- pop ebp:弹出之前保存的旧ebp值,恢复上一个栈帧的基指针。
- 返回地址:
- ret:从栈中弹出返回地址并跳转回调用者的代码中继续执行。
这个过程是由编译器自动完成的。编译器回在编译时生成相应的汇编指令来管理栈帧的创建和销毁。当函数返回时,通过移动栈顶指针(esp)来"销毁"局部变量,实际上是将它们占用的内存标记为可用,而不是物理上的清除。这样,下一次函数调用时,新的局部变量就可以重用这部分内存。
这种自动化的内存管理机制使得程序员无需手动管理每个函数的存储释放,简化了编程工作并防止了内存泄漏。
当我再次面对那个问题时,我已经不再是原来的我。我学会了,在编程的世界里,每一次的深入都是一次全新的蜕变。感谢那位大佬,让我知道了,学习,永远不应该止步。在代码的海洋里,还有无数的奥秘等待我去揭开。🚀