附青云而上

.net/c#中栈和堆的区别及代码在栈和堆中的执行流程详解之一

.net中的代码执行时,在内存中的栈和堆这两个地方用来存储这些代码,它们驻留在机器内存中,且包含所有代码执行所需要的信息。
注:下图中的STACK代表栈,HEAP代表堆。
1...net/c#中栈和堆有什么不同?
栈和堆的演示图:

图1
由上图1所示:
       可以将栈想象成一堆从顶向下堆叠的盒子。每当调用一次方法时,我们将应用程序中所要发生的事情记录在栈顶的一个盒子中,而我们每次只能够使用栈顶的那个盒 子。当我们栈顶的盒子被使用完之后,或者说方法执行完毕之后,我们将抛开这个盒子然后继续使用栈顶上的新盒子。因此,栈是自行维护的,也就是说内存自动维护栈,当栈顶的盒子不再被使用,它将被抛出。
         堆的工作原理比较相似,但大多数时候堆用作保存信息而非保存执行路径,因此堆能够在任意时间被访问。与栈相比堆没有任何访问限制,堆就像床上的旧衣服,我 们并没有花时间去整理,那是因为可以随时找到一件我们需要的衣服,而栈就像储物柜里堆叠的鞋盒,我们只能从最顶层的盒子开始取,直到发现那只合适的。因 此,堆需要考虑垃圾回收,垃圾回收用于保持堆的整洁性。
2..net/c#中栈和堆分别存放什么东西?
栈和堆中主要用来存放四种基本类型的数据,分别是:
值类型(Value Type),引用类型(Reference Type),指针(Pointer),指令(Instruction)。
  • c#中的值类型有:
    bool   byte   char   decimal   double   enum
    float   int   long   sbyte   short   struct   uint   ulong   ushort
  • c#中的引用类型有:
    class   interface   delegate   object   string
  • c#中的指针也就是类型引用,由公共语言运行时(CLR)来管理。指针/引用和栈与堆一样,是要占用内存空间的,它的值其实是一个内存地址或者为空。
    下图2分别演示了指针在栈中和在堆中分别指向一个地址的例子:

    图2
那么这些类型存放在哪里呢?
>引用类型都是放在堆里的;
>值类型和指针总是放在它们被声明的地方。(首先需要知道栈是如何工作的,然后才能断定是在哪儿被声明的。下面将会详细介绍)
程序举例1(值类型声明在方法体中):
publicint AddFive(int pValue)
          {
                int result;
                result = pValue + 5;
               
returnresult;
          }

我们来看下这个方法在栈里的结构图(图3):
图3
由图所知:方法先入栈(注意:这里的方法入栈是只包含需要执行的逻辑字节,即执行该方法的指令,而非方法体内的数据),然后是参数入栈。
接着是控制(即执行方法的线程)被传递到堆栈中AddFive()的指令上,如图(图4):

图4
当方法执行时,我们需要在栈上为“result”变量分配一些内存,如图(图5):

图5
最后,方法执行完成,然后方法的结果被返回,如图(图6):
图6
方法执行完后,栈上的该方法所使用内存都被自动清空,因此我们不用考虑回收内存。(图略)
由此例子可知:在方法体中声明的值类型是放在栈上的。对应了上面提到的《值类型和指针总是放在它们被声明的地方。
OK,接下来看另一个例子2(值类型声明在类中):
publicclass MyInt
          {
         
            
publicint MyValue;
          }
public MyInt AddFive(int pValue)
          {
                MyInt result = new MyInt();
                result.MyValue = pValue + 5;
                return result;
          }
函数执行时还是我们上面所提到的,方法和参数入栈,然后控制栈中AddFive的指令上,如图7所示:
图7
因为"MyInt"是一个引用类型,它将被放置在堆上,同时在栈上生成一个指向这个堆的指针引用。 如图8所示:
图8
由上图8所知,在类中声明的值类型存放在了堆中,这又应对了我们上面所提到的《值类型和指针总是放在它们被声明的地方。
方法执行完毕后,它在栈中所占的内存将被清空,剩下MyInt对象在堆中(栈中将不会存在任何指向MyInt对象的指针!)
所下图9所示:
大家可能想到了,久而久之,堆里就存放了很多栈中没有指向它的“垃圾”了,呵呵,垃圾回收机制就要起作用啦!
当我们的程序达到了一个特定的内存阀值,我们需要更多的堆空间的时候,垃 圾回收机制开始起作用。它将停止所有正在运行的线程,找出在堆中存在的所有不再被主程序访问的对象,并删除它们。然后垃圾回收机制会重新组织堆中所有剩下 的对象来节省空间,并调整栈和堆中所有与这些对象相关的指针。当然,这个过程非常耗费性能啦!所在我们在写代码时可以自己来释放堆中的垃圾而不要领先系统 自己的垃圾回收,呵呵。
那么引用类型和值类型又有什么区别呢?
根本:当我们使用引用类型时,我们实际是在处理该类型的指针,而非该类型本身。当我们使用值类型时,我们是在使用值类型本身。
如果您看不懂很正常,看实例吧:
publicintReturnValue()
          {
                intx = newint();
                x = 3;
                inty = newint();
                y = x;      
                y = 4;         
               
returnx;
    
      }
以上方法执行结果为3,您可能也会得到此值。看下它在栈中的结构图示:
如图所示,x与y 在栈中被分配了不同的内存空间,所以它们的值互不影响,改变y的值并不会对x的值造成影响的。
由此证明上面提到的(当我们使用值类型时,我们是在使用值类型本身。)
再看实例:
publicclassMyInt
          {

                publicintMyValue;
          }

publicintReturnValue2()
          {
                MyInt x = new MyInt();
                x.MyValue = 3;
                MyInt y = new MyInt();
                y = x;                 
                y.MyValue = 4;              
                
returnx.MyValue;
           }

大家想想这个实例程序结果应当是多少,想清楚再说啊,应当是4,呵呵,为什么会是4呢,看下它们在栈和堆中的分布图就明白了:
由上图而知:变量x和变量y同时指向堆中的内存相同的位置,改变y的值x的值当然要受到影响啦。
由此证明(当我们使用引用类型时,我们实际是在处理该类型的指针

posted on 2009-01-01 14:55  华丽的低调  阅读(309)  评论(0)    收藏  举报

导航