.Net/C#基础|C#深入理解堆和栈的区别

C#深入理解栈(stack)和堆(heap)的区别 在讲栈和堆之前,我们先看看值类型和引用类型:

类别 描述
值类型 基本数据类型 枚举类型 结构类型
引用类型 类 接口 数组
1)值类型:值类型总是分配在它声明的地方,作为局部变量时,存储在栈上;作为类对象的字段时,则跟随此对象存储在堆中。
2)引用类型:引用类型存储在堆中。类型实例化的时候,会在堆中开辟一部分空间存储类的实例。类对象的引用(地址)还是存储在栈中。
class Program { static void Main(string[] args) { byte b = 0xff; //byte类型 在栈中占 1个字节UInt16 c = 0xffff; //uint16类型 在栈中占 2个字节//person:对象的引用,存储在栈中 //new Person():对象的实例,存储在堆中 Person person = new Person(); //引用类型//a:局部变量,存储在栈中 int a = 10; //值类型 Console.ReadKey(); } }public class Person { //类的字段,跟随此类存储在堆中 public int Age { get; set; }//值类型 }

上面写了一些很简单的代码,我们运行起来并查看了变量在栈中的地址:
&b 0x001af368 *&b: 0xff &c 0x001af364 *&c: 0xffff &person 0x001af360 *&person: 0x02562390 &a 0x001af35c *&a: 0x0000000a

我们发现了几条规律:
1)先声明的变量在栈中的地址比较大,后声明的变量在栈中的地址比较小
2)在栈中,对于值类型来说,那块地址存储的就是变量的值;而对于引用类型来说,存储的是对堆的引用(在堆中的地址)
.Net/C#基础|C#深入理解堆和栈的区别
文章图片

  • 栈的结果是先进后出。与生命周期相关,越靠近栈底部的(栈地址越大的声明周期越长)
  • 栈地址是从高往低分配的,按照先后定义的顺序依次压入栈中,相邻变量的地址之间不会存在其他变量。
  • 栈是有操作系统自动分配释放的(作为程序员,你不需要显示对它做任何事),用于存储变量的值、程序执行的当前环境、方法的参数和类型的引用等。
  • 堆里的内存能够任意顺序的存入和移除,地址一般是从低往高分配。
  • 堆里的数据由CLR的自动GC在判断出程序的代码将不会再访问某项数据项时,自动清除无主的堆对象。
  • 在程序运行的时候,每一个线程都会维护一个自己的专属线程堆栈(就是我们说的栈)。当一个方法被调用的时候,主线程开始在所属程序集的元数据中,查找被调用方法,然后通过JIT即时编译并把结果(一般是本地CPU指令)放在栈顶,CPU通过总线从栈顶取指令,驱动程序以执行下去。
  • 【.Net/C#基础|C#深入理解堆和栈的区别】栈通常存储着我们代码执行的步骤,而堆上存放的则多是对象、数据等。在调用方法的时候,内存从栈的顶部开始分配,保存和方法关联的一些数据项,这块内存叫做方法的栈帧(stack frame)。栈帧包含的内存保存如下内容:
    1)返回地址,也就是在方法退出的时候继续执行的位置。
    2)这些参数分配的内存,也就是方法的值参数,或者还可能时参数数组。
    3)各种和方法调用相关的其他管理数据项。
    在方法调用时,整个栈帧都会压入栈;在方法推出的时候,整个栈帧都会从栈顶弹出,弹出栈帧有的时候也叫做栈展开。
    栈内存无需我们管理,也不受GC管理。当栈顶元素使用完毕,立马释放。而堆则需要GC清理,它像一个仓库,存储着我们使用的各种对象信息,跟栈不同的是他们被调用完毕不会立即被清理掉。

    推荐阅读