C# 深入理解堆栈、堆在内存中的实现

发表于:2017-7-28 10:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:未知    来源:博客

  * 如何决定放哪儿?
  这里有一条黄金规则:
  1. 引用类型总是放在堆中。(够简单的吧?)
  2. 值类型和指针总是放在它们被声明的地方。(这条稍微复杂点,需要知道栈是如何工作的,然后才能断定是在哪儿被声明的。)
  就像我们先前提到的,栈是负责保存我们的代码执行(或调用)时的路径。当我们的代码开始调用一个方法时,将放置一段编码指令(在方法中)到栈上,紧接着放置方法的参数,然后代码执行到方法中的被“压栈”至栈顶的变量位置。通过以下例子很容易理解...
  下面是一个方法(Method):
  public int AddFive(int pValue)
  {
  int result;
  result = pValue + 5;
  return result;
  }
  现在就来看看在栈顶发生了些什么,记住我们所观察的栈顶下实际已经压入了许多别的内容。
  首先方法(只包含需要执行的逻辑字节,即执行该方法的指令,而非方法体内的数据)入栈,紧接着是方法的参数入栈。(我们将在后面讨论更多的参数传递)
  接着,控制(即执行方法的线程)被传递到堆栈中AddFive()的指令上,
  当方法执行时,我们需要在栈上为“result”变量分配一些内存,
  Themethod finishes execution and our result is returned.
  方法执行完成,然后方法的结果被返回。
  通过将栈指针指向AddFive()方法曾使用的可用的内存地址,所有在栈上的该方法所使用内存都被清空,且程序将自动回到栈上最初的方法调用的位置(在本例中不会看到)。
  在这个例子中,我们的"result"变量是被放置在栈上的,事实上,当值类型数据在方法体中被声明时,它们都是被放置在栈上的。
  值类型数据有时也被放置在堆上。记住这条规则--值类型总是放在它们被声明的地方。好的,如果一个值类型数据在方法体外被声明,且存在于一个引用类型中,那么它将被堆中的引用类型所取代。
  来看另一个例子:
  假如我们有这样一个MyInt类(它是引用类型因为它是一个类类型):
  public class MyInt
  { 
  publicint 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...这太棒了, 当它是如何影响我的?
  Goodquestion.
  当我们使用引用类型时,我们实际是在处理该类型的指针,而非该类型本身。当我们使用值类型时,我们是在使用值类型本身。听起来很迷糊吧?
  同样,例子是最好的描述。
  假如我们执行以下的方法:
  public int ReturnValue()
  {
  int x = new int();
  x = 3;
  int y = new int();
  y = x; 
  y = 4; 
  return x;
  }
  我们将得到值3,很简单,对吧?
  假如我们首先使用MyInt类
  public class MyInt
  {
  public int MyValue;
  }
  接着执行以下的方法:
  public int ReturnValue2()
  {
  MyInt x = new MyInt();
  x.MyValue = 3;
  MyInt y = new MyInt();
  y =x; 
  y.MyValue =4; 
  return x.MyValue;
  }
  我们将得到什么?... 4!
  为什么?... x.MyValue怎么会变成4了呢?... 看看我们所做的然后就知道是怎么回事了:
  在第一例子中,一切都像计划的那样进行着:
  public int ReturnValue()
  {
  int x = 3;
  int y = x; 
  y = 4;
  return x;
  }
  
在第二个例子中,我们没有得到"3"是因为变量"x"和"y"都同时指向了堆中相同的对象。
  public intReturnValue2()
  {
  MyInt x;
  x.MyValue = 3;
  MyInt y;
  y =x; 
  y.MyValue = 4;
  return x.MyValue;
  }
  
   希望以上内容能够使你对C#中的值类型和引用类型的基本区别有一个更好的认识,并且对指针及指针是何时被使用的有一定的基本了解。在系列的下一个部分,我们将深入内存管理并专门讨论方法参数。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号