C#堆栈原理(我有两个例子测试你到底会不会)
背景
上次写了一篇文章关于try finnally的一些疑问(被我用windows live覆盖了,草),后来经过大神们解释,我明白了在我理解了try、finnally运行原理后,还欠缺的就是关于值类型引用类型在内存中的存储问题。
我仔细拜读了一些大神们的文章,主要的参考点是一个系列文章《译文---C#堆VS栈》,里面详细解释了堆栈的要点,基础差的禽兽们就别看我这个总结了。
什么是堆栈
堆栈在内存中可以理解为两个不同的容器。
栈:栈就是一个先入后出的存储空间,必须按照顺序依次执行。比如一筒羽毛球,只有一个出入口,如果想取出来球,只能先把最后塞入的那个球拿出来才能继续拿其他的球。
堆:堆也是一个按序排列的存储空间,不同的是,堆里面的数据可以随便取。比如图书馆里的书都是按照一定顺序排列的,但是取书的时候,可以直接拿出来自己需要的书籍而不用管其他的书籍。
数据在堆栈中的存储位置
在堆栈上保存的数据分为四种:值类型、引用类型、指针、指令。
值类型重点关注struct,因为其容易被认为成引用类型,而且其存储的数据有时候很大,传值时最好用ref;
引用类型关注string,因为其虽然是引用类型,但是表现上是值类型,即通过形参传入的string数据的操作不会影响实参。
指针通常不会由我们显示的使用,它们由公共语言运行时(CLR)来管理。指针(或引用)是不同于引用类型的,是因为当我们说某个事物是一个引用类型时就意味着我们是通过指针来访问它的。指针是一块内存空间,而它指向另一个内存空间。就像栈和堆一样,指针也同样要占用内存空间,但它的值是一个内存地址或者为空。(我复制的,我也不太懂)
指令,在我参考的文档里好像并没有提到,我理解的是,当JIT编译器把方法的IL语句变为机器语言后,会将其存入到一个动态分配的内存空间,然后将地址返回给CLR的一个内部数据结构表(此表记录一个类里面的所有方法的地址),因为指令相当于值类型,具体存储位置可以参考值类型。
类型存在位置的定律
-
引用类型总是在堆上创建。
-
值类型和指针类型总是在它声明的地方创建。
引用类型总是在堆上创建,然后将地址返回,地址的保存位置类似与值类型。
值类型的存储位置和声明它的类型有关。当Main()执行的时候,会分配一个栈区,如果在Main()里面声明了值类型,那么它就会保存在这个栈上;如果Main()先声明了一个struct,而struct里面声明了值类型,那么这个值类型也一样保存在栈上,因为声明这个值类型的类型即struct也在栈上;而如果声明值类型的类型是一个引用类型,比如一个类,那么这个值类型就会保存在堆上。
赋值时的区别
值类型赋值就是一种深拷贝,即将值的内容传递过去,且不会受相互操作的影响。
引用类型赋值是一种浅拷贝,只是将引用类型的地址传递了过去,比如A = B,那么A、B的操作都会相互影响。当然,string是一种特例,赋值或者传参后不会相互影响。如果你想引用类型也能执行深拷贝,就要自己写Clone()方法了。
空间分配及回收
堆和栈这里的区别具体不解释,可以查看相关文档,这里引用一位前辈的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。比喻很形象,说的很通俗易懂,不知道你是否有点收获。
举例
以下是我想的两个例子,如果你都能答对且明白原理,则说明你至少理解了堆栈。
1、引用类型赋值,修改某一个引用类型,会有什么结果
static void Main(string[] args) { var my = new MyInt(); var you = my; you.index = 1; you = null; Console.WriteLine(my.index.ToString()); Console.ReadLine(); } public class MyInt { public int index; }
结果是:1;而且没有返回异常。(重点是为什么没发生异常)?
2、引用类型传递和指针传递(ref代表传递的是指针)的区别
static void Main(string[] args) { Program main = new Program(); var my = new MyInt(); main.CommonMethod(my); Console.WriteLine(my.index.ToString()); main.RefMethod(ref my); Console.WriteLine(my.index.ToString()); Console.ReadLine(); } public void CommonMethod(MyInt myInt) { myInt.index = 1; myInt = null; } public void RefMethod(ref MyInt myInt) { myInt.index = 2; myInt = null; } public class MyInt { public int index; }
结果是1,然后异常;(为什么引用类型传递不异常,而指针传递异常)?
疑问
数据结构的栈和内存中的栈有什么区别吗?网上说数据结构的栈是一个理论,一个是实现方式,那么数据结构中的队列,在内存中有什么实现呢?还是说网上的说法是错误的?
希望大神们帮我解释一下。