原文链接:https://jonskeet.uk/csharp/memory.html

  人们在理解值类型和引用类型之间的差异时因为“值类型在栈上分配,引用类型在堆上分配”这句话造成了很多混乱。这完全是不对的,本文试图澄清这个问题。

变量中有什么?

  理解.NET中内存工作方式的关键是理解变量是什么,以及它的值是什么。在最基本的层面上,变量是变量名和内存之间的关联。变量的值是与之关联的内存中的内容。该值占用内存空间的大小和值的解释取决于变量的类型 - 这正是值类型和引用类型之间的差异所在。

  引用类型变量的值始终是引用或null。如果是引用,则它必须是与其变量类型兼容的对象的引用。例如,以Stream s声明的变量s的值是null或Stream类型(或其兼容类型)实例的引用。引用类型变量所占内存空间的大小是引用的大小,引用的大小在32位模式下固定为4个字节,在64位模式下固定为8个字节。

  值类型变量的值始终是其对象本身的值。例如,对于给定的结构:

struct PairOfInts
{
public int a;
public int b;
}

  以PairOfInts pair声明的变量pair的值是整数对本身,而不是对一对整数的引用。其所占内存空间则是两个整数的大小,即8个字节。请注意,值类型变量永远不能赋值为null - 因为这没有任何意义,值类型变量不是一个引用。

那么东西存放在哪里?  

  变量的分配位置取决于声明它的上下文:

  • 局部变量在栈上分配。这包括引用类型变量 - 变量本身位于栈上,其引用的值分配在堆上。方法参数也计为局部变量,但如果使用ref、out、in修饰符修饰它们,则它们不再是原始类型,而是转换为托管指针类型(Type &),此时传递的是原变量的指针,不再是变量本身。
  • 引用类型的对象始终在堆上分配。
  • 值类型的对象始终内联分配。即在方法中声明的值类型变量在栈上分配,而作为类的实例字段的值类型变量将在堆上分配。
  • 静态变量在堆上分配,包括引用类型和值类型中声明的静态变量。无论创建多少个实例,静态变量都共享一个内存空间。

  上述规则有几个例外:在使用匿名方法时的外部变量和迭代器中的局部变量会由编译器优化为其它类型的实例字段,这些变量会转移到堆中分配。

举个例子

  上述文字描述可能听起来有点复杂,但一个完整的例子可以让事情更清楚一些:

using System;

struct PairOfInts
{
static int counter = ; public int a;
public int b; internal PairOfInts(int x, int y)
{
A = x;
B = y;
counter++;
}
} class Test
{
PairOfInts pair;
string name; Test(PairOfInts p, string s, int x)
{
pair = p;
name = s;
pair.a + = x;
} static void Main()
{
PairOfInts z = new PairOfInts(, );
Test t1 = new Test(z, "first", );
Test t2 = new Test(z, "second", );
Test t3 = null;
Test t4 = t1;
//XXX
}
}

  让我们看一下标记“XXX”位置时内存中的内容。

  • 在栈上分配一个PairOfInts类型的对象,对应变量z。
  • 在堆上分配一个Test类型的对象,在栈上分配一个引用指向该对象,对应变量t1。以32位模式举例,该对象在堆中占用20个字节:8个字节的头信息(所有堆对象都有),8个字节用于存储PairOfInts实例,4个字节用于存储字符串引用。
  • 在堆上分配一个Test类型的对象,在栈上分配一个引用指向该对象,对应变量t2。该对象与上面的对象非常相似。
  • 在栈上分配一个引用,对应变量t3。这个引用是null - 它没有引用任何对象。
  • 在栈上分配一个引用,对应变量t4,并赋值t1引用的对象,此时t1和t4引用堆内存中的同一个对象。
  • 最后,在堆内存中有一个静态变量PairOfInts.counter。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!

作者:Minotauros
出处:https://www.cnblogs.com/minotauros/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[译].Net中的内存的更多相关文章

  1. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  2. c++继承中的内存布局

    今天在网上看到了一篇写得非常好的文章,是有关c++类继承内存布局的.看了之后获益良多,现在转在我自己的博客里面,作为以后复习之用. ——谈VC++对象模型(美)简.格雷程化    译 译者前言 一个C ...

  3. C++继承 派生类中的内存布局(单继承、多继承、虚拟继承)

    今天在网上看到了一篇写得非常好的文章,是有关c++类继承内存布局的.看了之后获益良多,现在转在我自己的博客里面,作为以后复习之用. ——谈VC++对象模型(美)简.格雷程化    译 译者前言 一个C ...

  4. 如何使用 DBCC MEMORYSTATUS 命令来监视 SQL Server 2005 中的内存使用情况

    https://technet.microsoft.com/en-us/solutionaccelerators/dd537566.aspx 注意:这篇文章是由无人工介入的微软自动的机器翻译软件翻译完 ...

  5. 浅入 .NET Core 中的内存和GC知识

    目录 托管代码 自动内存管理 参考资料: [1]https://docs.microsoft.com/zh-cn/dotnet/standard/managed-code [2]:https://do ...

  6. Java中堆内存和栈内存详解2

    Java中堆内存和栈内存详解   Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...

  7. Unity游戏开发中的内存管理_资料

    内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...

  8. Java中堆内存和栈内存详解

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...

  9. C++中的内存管理

    在C++中也是少不了对内存的管理,在C++中只要有new的地方,在写代码的时候都要想着delete. new分配的时堆内存,在函数结束的时候不会自动释放,如果不delete我分配的堆内存,则会造成内存 ...

随机推荐

  1. 一个基于jQuery写的弹窗效果(附源码)

    最近项目中频繁遇到需要弹出窗口的功能,一直使用浏览器默认的Alert和Confirm弹窗,感觉视觉效果不是那么好,而从网上下载的话又找不到合适的,找到的话有些也是十分臃肿,有时候感觉学习配置的功夫自己 ...

  2. java.lang.ClassNotFoundException: org.hibernate.ejb.HibernatePersistence 解决方法

    编译遇到错误,如下: May 11, 2017 1:49:42 PM org.springframework.web.context.ContextLoader initWebApplicationC ...

  3. javaweb各种框架组合案例(三):maven+spring+springMVC+hibernate

    1.hibernate译为"越冬",指的是给java程序员带来春天,因为java程序员无需再关心各种sql了: 2.hibernate通过java类生成数据库表,通过操作对象来映射 ...

  4. selenium工作原理详解

    selenium简介 Selenium是一个用于Web应用程序自动化测试工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, 9, 10, 11), ...

  5. PWN菜鸡入门之栈溢出 (2)—— ret2libc与动态链接库的关系

    准备知识引用自https://www.freebuf.com/articles/rookie/182894.html 0×01 利用思路 ret2libc 这种攻击方式主要是针对 动态链接(Dynam ...

  6. never下ioc

    生命周期 当前分单例,作用域(范围),短暂.单例是整个服务中只有一个实例,短暂则是每一次得到的都是新的实例,作用域就是在该一套行动中内得到的是同一个实例,该行动中指的是什么?我们看看demo下的sta ...

  7. Go语言学习——channel的死锁其实没那么复杂

    1 为什么会有信道 协程(goroutine)算是Go的一大新特性,也正是这个大杀器让Go为很多路人驻足欣赏,让信徒们为之欢呼津津乐道. 协程的使用也很简单,在Go中使用关键字“go“后面跟上要执行的 ...

  8. LeetCode刷题------------------------------LeetCode使用介绍

    临近毕业了,对技术有种热爱的我也快步入码农行业了,以前虽然在学校的ACM学习过一些算法,什么大数的阶乘,dp,背包等,但是现在早就忘在脑袋后了,哈哈,原谅我是一枚菜鸡,为了锻炼编程能力还是去刷刷Lee ...

  9. 西安7月21日「拥抱开源,又见.NET:壹周年Party」线下交流活动

    本次活动既是.NET西安社区的第四次线下交流活动,也是.NET西安社区成立一周年庆活动..NET西安社区2018年7月20日成立,经过一年时间的发展,社区共举办过3次大型线下交流活动,社区人数由最初的 ...

  10. Azkaban 3.73.1 集群搭建(Multiple Executor)

    一.简介 Azkaban是LinkedIn开源的任务调度框架,采用java编写 Azkaban功能和特点: 任务的依赖处理 任务监控,失败告警 任务流的可视化 任务权限管理 Azkaban具有轻量可插 ...