1、前言
在编写代码中,我们经常回调用这样或那样的方法(函数),但是我们真的了解各种参数类型在内存上市如何分配的么?不见得,至少在这之前我就不是很了解。我们也许认为这只是一个很小的问题,觉得知道方法是做什么的、会调用方法就行,所以就忽略了这一点。下面就了解一下方法中的参数是如何在内存上分配的。如有不妥之处,还请各位大牛指点。
2、值类型与引用类型的分配
一般来说,值类型是分配在栈中,而引用类型则在堆中分配存储单元。栈在编译的时候就分配好内存空间,在代码中有栈的明确定义,而堆是程序运行中动态分配的内存空间,可以根据程序的运行情况动态地分配内存的大小。因此,值类型总是在内存中占用一个预定义的字节数(比如,int占用4个字节,即32位)。当声明一个值类型变量时,会在栈中自动分配此变量类型占用的内存空间,并存储这个变量所包含的值。.NET会自动维护一个栈指针,它包含栈中下一个可用内存空间的地址。栈是先入后出的,栈中最上面的变量总是比下面的变量先离开作用域。当一个变量离开作用域时,栈指针向下移动被释放变量所占用的字节数,仍指向下一个可用地址。注意,值类型的变量在使用时必须初始化。
而引用类型的变量则在栈中分配一个内存空间,这个内存空间存储的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。.NET也自动维护一个堆指针,它包含堆中下一个可用内存空间的地址,但堆不是先入后出的,堆中的对象不人在程序的一个预定义点离开作用域,为了在不使用堆中分存的内存时将它释放,.NET将定期执行垃圾收集。垃圾收集器递归地检查应用程序中所有的对象引用,当发现引用不再有效的对象使用的内存无法从程序中访问时,该内存就可以回收(除了fixed关键字固定在内存中的对象外)。
但值类型在栈上分配内存,而引用类型在托管堆上分配内存,却只是一种笼统的说法。下面是网上找到的更详细准确地描述:
1、对于值类型的实例,如果作为方法中的局部变量,则被创建在线程栈上;如果该实例做为类类型的成员,则作为类型成员的一部分,连同其他类型字段都存放在托管堆上;
2、引用类型的实例创建在托管堆上,如果其字节小于85000byte,则直接创建在托管堆上,否则创建在LOH(Large Objet Heal)上。
关于引用类型的存储,我觉得上面的第二点说的还是不够详细,如果引用类型不是一个类类型的成员,那么它的存储被分为两部分,一部分是引用部分,存储在栈上并指向具体的堆地址,另一部分是实际的数据部分,存储在堆上;如果引用类型是一个类类型的成员,那么无论是引用部分还是数据部分,都会作为类类型的实例存储在堆上。
1 public class MyClass 2 { 3 private int i; //即使变量i是值类型,但它作为MyClass(引用类型)实例的一部分,与MyClass的实例一起被创建在GC堆上 4 public MyClass() 5 { 6 int j = 0; //作为局部变量,j的实例被创建在执行这段代码的线程栈上 7 } 8 }
3、数组中值类型和引用类型的内存分配
数组分为值类型和引用类型,见下面的代码段:
1 public class Person 2 { 3 private int age; 4 private string name; 5 6 public int Age 7 { 8 get; 9 set; 10 } 11 12 public string Name 13 { 14 get; 15 set; 16 } 17 }
1 Person[] person=new Person[5];//引用类型数组 2 int[] num=new int[5];//值类型数组
数组也是引用类型,所以数组类型的变量被分配在堆上。
对于上述int类型(简单的值类型的数组),每个数组成员是一个引用(指针),引用到栈上的空间(因为值类型变量的内存分配在栈上);
对于上述Person类类型(引用类型,类类型的数组),每个数组成员仍是一个引用(指针),引用到堆上的空间(因为类的实例的内存分配在堆上)
4、总结
最近在看这方面的资料,总觉得还是写出来会加深印象,另一方面,可以为初学者提供思路。以上是关于值类型和引用类型的内存分配方式,如果有什么不对的地方,请各位大牛指出,我会学习并作相应修改。
我会在下一篇介绍关于具体的方法参数的内存分配方式,谢谢大家!!