在.NET中或许我们不用担心内存管理以及垃圾回收器(Garbage Collection GC)的问题,但是我们还是应该了解这些东东以便在必要的时候优化我们程序的性能。而且,如果对内存管理如何工作有所了解,那将有助于解释我们每个程序里的每个变量的运行规律。这篇文章主要内容是解释堆(Heap)和栈(Stack),各种变量以及这些变量到底是如何工作的。

.Net Framework 在执行代码时,有两个用来存储对象的地方,也就是堆和栈,用于帮助执行我们的代码。它们驻留在机器内存中,包含了所有我们需要实现的信息。

Stack VS Heap

栈多多少少用来负责跟踪你的代码里正在执行什么,或者说代码里的什么东东被called。而堆则或多或少用来跟踪我们的对象,或者说数据,大多数情况下都是数据啦——后头再详解。

把栈想象成堆砌起来由上到下的盒子。每次我们调用一个方法,就新加一个盒子到栈顶,我们用这种方法跟踪我们的程序在执行些什么。我们能用的,永远只是最顶上的那个盒子。当我们把最顶上这个盒子用掉了的时候,也就是方法执行完毕并返回的时候,我们就恶狠狠的把它扔掉!然后接着处理下一个盒子里的东东。而堆其实也是类似的东东,除了它的目的是用来保存信息(绝大多数情况下不是用来跟踪程序执行),因此堆里面的任何东西都不受限制的随便访问。堆就像是床上我们没空收拾的洗好的衣服一般,我们能很快的随便拿任何一件起来。而栈则跟壁橱里一堆装鞋子的盒子似的,我们得一个一个的从顶上取下来才能拿到下一双鞋子。

上图虽然并不是真正的内存中堆栈的样子,不过有助于我们理解它们的区别。

栈是“自我维护”的,意思是基本上是管理自个儿的(而不是别的地方的)内存。当顶部盒子不在使用,就扔之(就不是自家的雪了)!而堆呢,不太一样的是,必须得跟GC打交道——这东西用来保证堆是clean(没有过多垃圾内存)的。(木有人喜欢地上摆一堆脏衣服吧!臭死了!)。

What goes on the Stack and Heap?

当代码执行时,堆栈里头主要放置四种类型的东东:值类型,引用类型,指针(Pointers),以及指令(Instructions)。

值类型:

c#中,值类型继承自System.ValueType:

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

引用类型:

而引用类型则有:

class, interface, delegate, object, string

指针:

内存管理模型中的第三种东东是对一个类型的引用。这个引用通常就是指指针。我们不能直接使用指针,它们被CLR所管理。指针不同于引用类型。当我们说某某是引用类型时,实际上就意味着我们要通过指针去访问这个类型的值。一个指针占用内存中的一块空间,只想内存中另外一块空间。指针跟任何别的放在堆栈中的东西一样,是要占用物理空间的。它的值要么是null,要么就是内存地址。

指令:

在后续文章中再解释,稍安勿躁……

How is it decided what goes where? (Huh?)

okok,再啰嗦两句我们就可以正式摆弄我们的堆栈了。

这里有两条黄金规则:

  1. 引用类型总是保存在堆里头——够清楚了吧
  2. 值类型以及指针,总是保存在其被声明(Declared)的地方。稍微复杂一丁点儿,因为需要理解什么是“其被声明的地方”

栈,就像我们刚才提到的,负责跟踪单个线程(thread)运行到哪儿了。你不妨把其想象成thread的状态机,每个线程都有自个儿的栈。当我们的代码调某个方法时,线程开始执行JIT编译过并且保存在方法表(method table)中的指令集,同时,它把方法参数压入线程栈中。然后开始执行代码并访问方法里需要的、同时已经存在于线程栈顶部的变量。举个例子吧:

public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}

让我们看看栈里头都发生了什么,记住我们看到的只是栈顶的东西,下头早有无数别的东东在里面了哦!

当我们开始执行这个方法时,方法的参数被压栈(稍后我们讨论参数传递)。

Notice: 方法并不存在stack里头,图例只是为了演示概念

下一步,控制(线程执行这个方法)被交给AddFive方法在方法表中的指令集,如果这是第一次使用这个方法,JIT编译将被执行。

在方法的执行过程中,我们需要内存来保存 "result”,因此栈顶为其分配空间。

方法结束了,result被返回。

这时栈顶指针将会移到最初AddFive方法开始的内存地址,这样所有刚才分配的内存空间都被清理掉了,然后接着执行AddFive更下面的函数(图中为显示)。

在这个例子里,result变量被压栈。事实上,任何时候值变量在方法内部被声明,都会被压栈。

不过,值变量有时候也会存储在堆里头。看看黄金规则二,值类型总是保存在被声明的地方。那么,如果值类型在方法(method)外部声明,同时本身又存在于引用类型内部时,那么就会被保存在堆里头。

在来一个例子:

如果我们有MyInt Class(引用类型):

public class MyInt
{
public int MyValue;
}

另外一个方法正在执行中:

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

跟前头一样,线程开始执行方法,参数被压栈:

这个时候开始好玩了:

因为MyInt是引用类型,因此保存在堆里头,通过栈里的指针去引用它。

AddFive执行完毕以后,我们开始清理栈顶:

这样我们就把MyInt对象当作孤儿留在了堆里头,因为栈中没有任何指针在引用它了!

这时候就是GC大显身手的时候啦!一旦我们程序达到某个特定的内存阀值(threshold)而我们需要更多堆空间时,GC就会开动。GC完全停止所有运行中的线程,找到所有堆里没有被引用的对象,然后像秋风扫落叶般的删除它们。GC重新组织所有滞留在内存中活动的对象,并调整堆栈中对其引用的地址。可以看到,从性能角度来说,这可真是很费时间。所以现在你是不是觉得注意一下堆栈的处理会有助于你写出高性能的代码呢?

OK,好了好了,很棒很棒,不过,这东西到底会如何来折磨俺们呢?

好问题。

当我们使用引用类型时,我们实际上是使用指向该对象的指针,而不是对象本身。当我们使用值类型时,我们使用的就是值本身。清楚还是不清楚?

最好还是来个例子吧。

如果我们执行以下方法:

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

我们得到3,够简单吧。

如果这样呢:

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。

为什么?

在第一个例子里所有事都按照预定计划行事:

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

在后面的例子里,我们得不到3是因为x和y都指向堆里的同一个对象。

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

希望本文能帮助您更好的理解值类型和引用类型基本的区别,以及指针是什么,什么时候会用到指针。在后面的系列中,我们进一步的阐述内存管理,特别的会多聊聊方法参数的问题。

转载于:https://www.cnblogs.com/tongdengquan/archive/2010/01/31/6090607.html

值类型与引用类型比较与区别相关推荐

  1. C# 值类型和引用类型的区别

    文章目录 二者在内存中的存储方式 二者区别的补充与总结 C# 的变量类型可以分为值类型和引用类型. 常见的值类型和引用类型可参考下面这个表格: 注:C# 的 struct 是值类型,这个很容易被忽略. ...

  2. 跟风写博---也谈值类型和引用类型

    在博客园潜水一年多,半年前开了博,但一直闲置着,前阵子看了老赵的博客二三事后,终于下了决定要认真写几篇.正巧最近园子在讨论"值类型和引用类型"相关的话题,我就谈谈个人见解. 直接从 ...

  3. 值类型与引用类型的区别

    可以这样理解: 值类型保存的是具体的值 引用类型保存的是值的地址 例如 int a = 1; int b = a; b++; 这时b为2,a仍然是1 再看引用类型,例如有个类型是 User User ...

  4. java引用类型和值类型_[Java教程]JavaScript中值类型和引用类型的区别

    [Java教程]JavaScript中值类型和引用类型的区别 0 2017-02-24 00:00:35 JavaScript的数据类型分为两类:原始类型和对象类型.其中,原始类型包括:数字.字符串和 ...

  5. Golang的值类型和引用类型的范围、存储区域、区别

    常见的值类型和引用类型分别有哪些? 值类型:基本数据类型 int 系列, float 系列, bool, string .数组和结构体struct,使用这些类型的变量直接指向存在内存中的值,值类型的变 ...

  6. swift string转int_swift中结构体和类的区别(值类型和引用类型的区别)

    在swift中结构体和类有着更多的相同之处,在一般的使用中能够做到互相替换.我们可以先看看官方文档的描述: Unlike other programming languages, Swift does ...

  7. C#类和接口、虚方法和抽象方法及值类型和引用类型的区别

    1.C#类和接口的区别 接口是负责功能的定义,项目中通过接口来规范类,操作类以及抽象类的概念! 而类是负责功能的具体实现! 在类中也有抽象类的定义,抽象类与接口的区别在于: 抽象类是一个不完全的类,类 ...

  8. 值类型和引用类型的区别

    一.定义 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值,那么调用这个方法是传入的变量的值也将改变. 值类型表示复制一个当前变量传给方法,当你 ...

  9. 值类型和引用类型及其区别!

    值类型和引用类型的基类 引用类型和值类型都继承自System.Object类.不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.Valu ...

最新文章

  1. 大规模中文多模态评测基准MUGE发布
  2. SAP Fiori 应用 My Appointment - Belonging to me, Search by team, Search by group
  3. 微课|玩转Python轻松过二级(2.1节):常用内置对象
  4. 【算法笔记】最短路-Dijkstra、Floyd、SPFA模版总结+复习
  5. 工业机器人技术基础与应用分析及编程基础
  6. C语言程序设计中十全十美,21世纪高校计算机应用技术系列规划教材 C语言程序设计-潭浩强主编 林小茶编著.pdf...
  7. Leetcode惊现马化腾每天刷题?为啥大佬都这么努力!
  8. mysql网站倒计时代码_最简单的一个网页倒计时代码 时间到期后会显示出提醒内容 收藏版...
  9. dna计算机的主要作用,我国首台DNA计算机 意义重大
  10. HDU 5285:wyh2000 and pupil
  11. 图解数据结构与算法【Java】0概述
  12. arduino学习笔记十四--Arduino 环境光线传感器实验
  13. VSPD+XCOM+Proteus仿真stm32串口通信
  14. 音乐播放器微信小程序开发-歌单界面(简陋版)
  15. c语言 10 9-8 7-6… 1循环语句,C语言学习与总结---第五章:循环结构程序设计
  16. R语言时间序列函数整理[不断更新]
  17. 虾扑 erp 有什么用?做 虾皮 如何快速上货?
  18. 手机android player病毒怎么解决,不要担心手机中毒!教您一些有关如何彻底清除Android手机上的病毒的提示...
  19. QGIS之十一导入CAD文件dwg/dxf
  20. 智能小车之PWM脉冲控制小车调速

热门文章

  1. java 执行外部命令 苹果_Java中执行外部命令
  2. 广义线性模型_算法小板报(四)——初探广义线性模型和最大熵模型
  3. JAVA复习5(集合——HashSet)
  4. oracle序列创建及使用,Oracle创建和使用序列
  5. ai边缘平滑_AI基础教程113:“效果”菜单之“画笔描边”(一)喷溅效果
  6. android搭建https,android 搭建https Server(示例代码)
  7. 计算机网络网关作用,默认网关的作用
  8. 后缀的形容词_高三语法总复习:名词形容词变名词的后缀
  9. python自动写作软件_开发了一款写作软件(OSX,Windows),附带Electron开发指南
  10. linux查看当前会话文件夹,Linux 记录会话过程的命令