.Net中堆与栈的区别(4部分全) (reprint)

英文出处:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory2B01142006125918PM/csharp_memory2B.aspx

2009/12/4
   19:00-20:40 看完部分一 速度有点慢,不过大致看懂了,金山词霸确实是个好东西```^_^
   20:40--22:00(部分二) 才节约了20分钟,汗! 其实速度是有提高的,主要是人比较累了,容易走神,呵呵
                 这章太经典了,我有种要翻译此文的感觉,^_^
   22:30 --23:15(部分三) 又节约了35分钟,不错,有进步!不过,只是大致看懂了,还需要细细的品,突然觉得
                 要翻译此文,有一定的难度了```` 觉得应该去看下中文的"深度复制"的资料````
   23:15--23:42(部分三) 又看了一遍又觉得可以写了,呵呵
12/5
   19:00-20:28(部分四) 部分四我想想还是先不要翻译了,先把前3个翻译再说
   22:00 突然发现网上有翻译此文的文章,英文看完了才发现@#$#%@````,Ok现在转载过来吧,呵呵

再次感谢作者写这么好的文章,也感谢翻译此文的人,翻译的同意经典 专业

由于整理的比较麻烦, 第1 2 3 4部分我放在word里了:下载

 

尽管在.NET framework下我们并不需要担心内存管理和垃圾回收(Garbage Collection),但是我们还是应该了解它们,以优化我们的应用程序。同时,还需要具备一些基础的内存管理工作机制的知识,这样能够有助于解释我们日常程序编写中的变量的行为。在本文中我将讲解栈和堆的基本知识,变量类型以及为什么一些变量能够按照它们自己的方式工作。

在.NET framework环境下,当我们的代码执行时,内存中有两个地方用来存储这些代码。假如你不曾了解,那就让我来给你介绍(Stack)和(Heap)。栈和堆都用来帮助我们运行代码的,它们驻留在机器内存中,且包含所有代码执行所需要的信息。


* 栈vs堆:有什么不同?

负责保存我们的代码执行(或调用)路径,而则负责保存对象(或者说数据,接下来将谈到很多关于的问题)的路径。

可以将想象成一从顶向下叠的盒子。当每调用一次方法时,我们将应用程序中所要发生的事情记录在顶的一个盒子中,而我们每次只能够使用顶的那个盒子。当我们顶的盒子被使用完之后,或者说方法执行完毕之后,我们将抛开这个盒子然后继续使用顶上的新盒子。的工作原理比较相似,但大多数时候用作保存信息而非保存执行路径,因此能够在任意时间被访问。与相比没有任何访问限制,就像床上的旧衣服,我们并没有花时间去整理,那是因为可以随时找到一件我们需要的衣服,而就像储物柜里叠的鞋盒,我们只能从最顶层的盒子开始取,直到发现那只合适的。



以上图片并不是内存中真实的表现形式,但能够帮助我们区分栈和堆

是自行维护的,也就是说内存自动维护,当顶的盒子不再被使用,它将被抛出。相反的,需要考虑垃圾回收,垃圾回收用于保持的整洁性,没有人愿意看到周围都是赃衣服,那简直太臭了!


* 栈和堆里有些什么?

当我们的代码执行的时候,栈和堆中主要放置了四种类型的数据:值类型(Value Type),引用类型(Reference Type),指针(Pointer),指令(Instruction)。

1.值类型:

在C#中,所有被声明为以下类型的事物被称为值类型:

  • bool 
  • byte 
  • char 
  • decimal 
  • double 
  • enum 
  • float 
  • int 
  • long 
  • sbyte 
  • short 
  • struct 
  • uint 
  • ulong 
  • ushort


2.引用类型:

所有的被声明为以下类型的事物被称为引用类型:

  • class 
  • interface 
  • delegate 
  • object 
  • string


3.指针:

在内存管理方案中放置的第三种类型是类型引用,引用通常就是一个指针。我们不会显示的使用指针,它们由公共语言运行时(CLR)来管理。指针(或引用)是不同于引用类型的,是因为当我们说某个事物是一个引用类型时就意味着我们是通过指针来访问它的。指针是一块内存空间,而它指向另一个内存空间。就像栈和堆一样,指针也同样要占用内存空间,但它的值是一个内存地址或者为空。



4.指令:

在后面的文章中你会看到指令是如何工作的...


* 如何决定放哪儿?

这里有一条黄金规则:

1. 引用类型总是放在中。(够简单的吧?)

2. 值类型和指针总是放在它们被声明的地方。(这条稍微复杂点,需要知道是如何工作的,然后才能断定是在哪儿被声明的。)

就像我们先前提到的,是负责保存我们的代码执行(或调用)时的路径。当我们的代码开始调用一个方法时,将放置一段编码指令(在方法中)到上,紧接着放置方法的参数,然后代码执行到方法中的被“压”至顶的变量位置。通过以下例子很容易理解...

下面是一个方法(Method):

现在就来看看在 顶发生了些什么,记住我们所观察的 顶下实际已经压入了许多别的内容。

首先方法(只包含需要执行的逻辑字节,即执行该方法的指令,而非方法体内的数据)入 ,紧接着是方法的参数入 。(我们将在后面讨论更多的参数传递)



接着,控制(即执行方法的线程)被传递到 堆栈中AddFive()的指令上,



当方法执行时,我们需要在 上为“result”变量分配一些内存,



The method finishes execution and our result is returned.
方法执行完成,然后方法的结果被返回。



通过将 指针指向AddFive()方法曾使用的可用的内存地址,所有在 上的该方法所使用内存都被清空,且程序将自动回到 上最初的方法调用的位置(在本例中不会看到)。



在这个例子中,我们的"result"变量是被放置在 上的,事实上,当值类型数据在方法体中被声明时,它们都是被放置在 上的。

值类型数据有时也被放置在 上。记住这条规则--值类型总是放在它们被声明的地方。好的,如果一个值类型数据在方法体外被声明,且存在于一个引用类型中,那么它将被 中的引用类型所取代。


来看另一个例子:

假如我们有这样一个MyInt类(它是引用类型因为它是一个类类型):

public   class  MyInt
{
public   int  MyValue;
}

 

然后执行下面的方法:

public  MyInt AddFive( int  pValue)
{
MyInt result 
=   new  MyInt();
result.MyValue 
=  pValue  +   5 ;
return  result;
}

 

就像前面提到的,方法及方法的参数被放置到 上,接下来,控制被传递到 堆栈中AddFive()的指令上。


接着会出现一些有趣的现象...

因为"MyInt"是一个引用类型,它将被放置在 上,同时在 上生成一个指向这个 的指针引用。


在AddFive()方法被执行之后,我们将清空...


我们将剩下孤独的MyInt对象在 中( 中将不会存在任何指向MyInt对象的指针!)


这就是垃圾回收器(后简称GC)起作用的地方。当我们的程序达到了一个特定的内存阀值,我们需要更多的 空间的时候,GC开始起作用。GC将停止所有正在运行的线程,找出在 中存在的所有不再被主程序访问的对象,并删除它们。然后GC会重新组织 中所有剩下的对象来节省空间,并调整 栈和堆中所有与这些对象相关的指针。你肯定会想到这个过程非常耗费性能,所以这时你就会知道为什么我们需要如此重视 栈和堆里有些什么,特别是在需要编写高性能的代码时。

Ok... 这太棒了, 当它是如何影响我的?

Good question.  

当我们使用引用类型时,我们实际是在处理该类型的指针,而非该类型本身。当我们使用值类型时,我们是在使用值类型本身。听起来很迷糊吧?

同样,例子是最好的描述。

假如我们执行以下的方法:

public   int  ReturnValue()
{
int  x  =   new   int ();
=   3 ;
int  y  =   new   int ();
=  x;
=   4 ;
return  x;
}

 

我们将得到值3,很简单,对吧?

假如我们首先使用MyInt类

public   class  MyInt
{
public   int  MyValue;
}

 

接着执行以下的方法:

我们将得到什么?...    4!

为什么?...  x.MyValue怎么会变成4了呢?...  看看我们所做的然后就知道是怎么回事了:

在第一例子中,一切都像计划的那样进行着:
public   int  ReturnValue()
{
int  x  =   3 ;
int  y  =  x;
=   4 ;
return  x;
}



在第二个例子中,我们没有得到"3"是因为变量"x"和"y"都同时指向了 中相同的对象。

public   int  ReturnValue2()
{
MyInt x;
x.MyValue 
=   3 ;
MyInt y;
=  x;
y.MyValue 
=   4 ;
return  x.MyValue;
}


希望以上内容能够使你对C#中的值类型和引用类型的基本区别有一个更好的认识,并且对指针及指针是何时被使用的有一定的基本了解。在系列的下一个部分,我们将深入内存管理并专门讨论方法参数。 

你可能感兴趣的:(print)