C#精要 - 堆栈篇
先说一下什么是栈?
数据结构中的栈是一种先进后出的数据结构。
在C#的内存中有用到栈结构,一个线程拥有一个大概1mb的线程栈。一般拿来存放引用指针、值类型、方法的局部变量和实参等。
内存管理
栈是一种内存自我管理的结构,压栈自动分配内存,出栈自动清空所占内存。
但因此栈只能在一端对数据进行操作,也就是栈顶端进行操作。
方法调用与线程栈
方法的调用追踪就是在栈上完成的。比如我们有一个main方法(程序入口), 在main方法中会调用一个GetPoint的方法。在线程执行时,会将main方法压入栈底(包括编译好的方法指令,参数,和方法内部变量),然后再将GetPoint的方法压入栈底,GetPoint中没有调用其它方法,压栈完毕。出栈顺序是先进后出,也就是后进先出,栈顶的方法GetPoint先执行完毕,然后出栈,所占内存清空,接着main方法执行后出栈,所占内存清空。
再说一下什么是堆?
数据结构中的堆,是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
CLR的堆是和栈对立的内存概念,而不是二叉树。一个程序用一个托管堆。托管堆一般拿来存放实例对象。
内存管理
相比栈只能在一端操作,堆中的数据可以随意存取。
因此堆的内存回收不是自我管理的,所以.NET推出了GC来管理堆内存。这里不展开GC。
说一下堆栈的协作?
1.当创建一个值类型时,CLR直接为其分配一个空间,这个空间分配在变量创建的地方:
- 如果值类型是在方法内部创建,则跟随方法入栈,分配到栈上存储。
- 如果值类型是引用类型的成员变量,则跟随引用类型,存储在堆上。
2.当创建一个引用类型时,CLR会在堆上分配对象,在栈上分配指针。
方法运行流程与堆栈?
执行一个方法比如
1 | void Start(){ |
1.进来这个方法的时候会将 方法的返回地址、方法体内的局部变量 都压入栈。
2.在方法内再遇到方法的时候,同样会做1一样的事:将此方法的返回地址、方法体内的局部变量压入栈。
3.执行完内部方法(return)后,CPU指令指针返回到记录的返回地址上,继续执行外部代码。
4.如果方法内遇到new对象的情况,会在堆中申请对象,然后栈上会有一个指向对象的指针。
栈帧
简单点说就是一个栈的暂时状态,它反映着进入方法前的栈内状态。进入某方法前会执行上面说的一系列操作,退出这个方法后,会展开栈帧,回到进来前的运行状态 和 代码执行位置(CPU指向)。
序幕代码
也就是步骤1、2做的事,将方法内部的局部变量、参数、和返回地址压入栈。