堆栈和托管堆以及装箱和拆箱的理解

一个人想着一个人 提交于 2020-01-28 07:16:56

C#中的类型都来源于system.object类型,分为值类型和引用类型,分别存在内存的堆栈和托管堆中,值类型一般都是简单类型如int float double等,他们保存在堆栈中,是按后进先出(LIFO)原则存储数据项的一种数据结构。在计算机系统中,栈特指处理器支持的一块内存区域,其中保存着局部变量。工作方式是先分配内存的变量后释放(先进后出原则),所以一旦出了作用域就会被释放,所以在整个项目中无法使用,这个时候就想到了托管堆。

堆(托管堆)存储引用类型。此堆非彼堆,.NET中的堆由垃圾收集器自动管理。与堆栈不同,堆是从下往上分配,所以自由的空间都在已用空间的上面。现在来举个例子看看在内存中是如何通过堆栈和托管堆保存数据的。

Int a=100;

那么在堆堆栈中就会分出一块空间用来保存a,值为100,现在有一个方法

Int GetNum(int b)

{

   b=500;

   Return b;

}

这个时候把a的值作为参数传给这个方法,那么此时a的值会不会变成500呢,这个就是我们重点讨论的问题,方式就是一个临时的,用完就会被释放,其实我们只是复制了一个a的到方法里了,所有a的值不会改变

Student stu=new Student();

我们知道上面的是一个引用类型的变量,它在内部的进程是

首先在堆栈中分出一块空间用来放Student stu的引用,然后将new Student()也就是对象stu放到堆中,而她的地址是保存到Student stu的引用中了,如下图

所以,如果有方法将引用类型的变量作为参数,就像上面似的,那么她的值会变的,因为它的参数只是一个引用,就比如人是一个引用,通用的熟悉都在人里所包含,假如人的熟悉改变,那么他的具体对象也将改变,下面在来看看自己对装箱和拆箱的理解:

1、装箱和拆箱是一个抽象的概念
2、装箱是将值类型转换为引用类型 ;拆箱是将引用类型转换为值类型

3、为何需要装箱?(为何要将值类型转为引用类型?)
一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱

4、装箱/拆箱是什么?
装箱:用于在垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。
拆箱:从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。

装箱:

   第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
第二步:将值类型的实例字段拷贝到新分配的内存中。
第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。

  比如:

    Int a=100;

    Object o=a;(装箱)

    a =200;

    那么这个过程就是现在托管堆中去分配一个内存,然后从堆栈复制一个a的实例到托管堆中刚分配的内存,最后将地址返回到堆栈中存放o引用的内存中去,这样就是该地址指向对象的引用了,所以无论你怎么改a的值 o的值都不会变化 相反你改o的值 a的值也不会变 因为他们存放的地址都不一样

拆箱:

检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。注意的是只有装过箱的对象才能被拆箱,否则会出现异常

比如可以将上面的对象拆箱:

a = (int)o;

这样就把o实例的值赋给a了,湖畔、或者重新分配一个内存空间存放j

Int j=(int)o;

6 装箱/拆箱对执行效率的影响
显然,从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。
那该如何做呢?
首先,应该尽量避免装箱。
比如上例2的两种情况,都可以避免,在第一种情况下,可以通过重载函数来避免。第二种情况,则可以通过泛型来避免。
当然,凡事并不能绝对,假设你想改造的代码为第三方程序集,你无法更改,那你只能是装箱了。
对于装箱/拆箱代码的优化,由于C#中对装箱和拆箱都是隐式的,所以,根本的方法是对代码进行分析,而分析最直接的方式是了解原理结何查看反编译的IL代码。比如:在循环体中可能存在多余的装箱,你可以简单采用提前装箱方式进行优化。

7通用类型系统(CTS)区分两种基本类型:值类型和引用类型。它们之间的根本区别在于它们在内存中的存储方式。.NET使用两种不同的物理内存块来存储数据—栈和托管堆

8 值类型总是在内存中占用一个预定义的字节数(例如,int类型占4个字节,而string类型占用的字节数会根据字符串的长度不同而不同),当声明一个值类型变量时,会在栈中分配适当大小的内存(除了引用类型的值类型成员外,如类的int字段),内存中的这个空间用来存储变量所含的值。.NET维护一个栈指针,它包含栈中下一个可用内存空间的地址。当一个变量离开作用域时,栈指针向下移动被释放变量所占用的字节数,所以它仍指向下一个可用地址

9 引用变量也利用栈,但这时栈包含的只是对另一个内存位置的引用,而不是实际值。这个位置是托管堆中的一个地址。和栈一样,它也维护一个指针,包含堆中下一个可用内存空间的地址。但是,堆不是先入后出的,因为对对象的引用可在我们的程序中传递(例如,作为参数传递给方法调用),堆中的对象不会在程序的一个预定点离开作用域。为了在不使用在堆中分配的内存时将它释放,.NET定期执行垃圾收集。垃圾收集器递归地检查应用程序中所有的对象引用。引用不再有效的对象使用的内存无法从程序中访问,该内存就可以回收。

10 引用类型包含一个指针,指向堆中存储对象本身的位置。因为引用类型只包含引用,不包含实际的值,对方法体内参数所做的任何修改都将影响传递给方法调用的引用类型的变量

 

上面只是自己的理解,有不对的地方请大家指出

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!