原文链接: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. ViewPager页面滑动,滑动到最后一页,再往后滑动则执行一个事件

    1.ViewPager在处理滑动事件的时候要用到OnPageChangeListener( 代码:this.viewPager.setOnPageChangeListener(new MyListen ...

  2. SYN1610型B码时统设备

       SYN1610型B码时统设备 产品概述 SYN1610型B码时统设备是由西安同步电子科技有限公司精心设计.自行研发生产的一款模块化配置的通用性时统终端,可接收北斗/ GPS信号/ IRIG-B码 ...

  3. vue实现Excel文件的上传与下载

    一.前言项目中使用到比较多的关于Excel的前端上传与下载,整理出来,以便后续使用或分析他人. 1.前端vue:模板下载与导入Excel 导入Excel封装了子组件,点击导入按钮可调用子组件,打开文件 ...

  4. CI框架注意

    1.在ci框架中,如果想继承自己写的控制器,从而不继承CI_Controller,可以在application/core中定义控制器,从而就可以在controllers中的控制器继承. 2.在ci框架 ...

  5. Java接口中的成员变量默认为(public、static、final)、方法为(public、abstract)

    interface”(接口)可将其想象为一个“纯”抽象类.它允许创建者规定一个类的基本形式:方法名.自变量列表以及返回类型,但不实现方法主体.接口也可包含基本数据类型的数据成员,但它们都默认为publ ...

  6. 23 | 知其然知其所以然:聊聊API自动化测试框架的前世今生

  7. 交叉编译多平台 FFmpeg 库并提取视频帧

    原文地址: 交叉编译多平台 FFmpeg 库并提取视频帧 交叉编译多平台 FFmpeg 库并提取视频帧 本文档适用于 x86 平台编译 armeabi.armeabi-v7a.arm64-v8a.x8 ...

  8. gmap 获取瓦片数

    在 gmap1.7版本中 gMapControl1.MapProvider.Projection.GetAreaTileCount(area, i, 0); 可用来获取到erea里i层的瓦片数量. 但 ...

  9. 从Spring的几个阶段理解其工作过程

      Spring框架非常强大,想要彻底弄懂Spring是非常困难的. 为了便于了解Spring的工作原理,我们来研究一下,Spring是怎么加载的,Spring会经过几个阶段. 我们站在Javaweb ...

  10. HTTP 学习笔记01

    HTTP   hypertext transfer protocol (超文本传输协议) TCP/IP 协议集中的一个应用层协议 用于定义WEB浏览器与WEB服务器之间交换数据的过程以及数据本身的格式 ...