引言

您是.Net工程师?那 .NetFramework中的类型您知道有三大类吗?(除了引用类型和值类型,还有?)

引用类型一定在“堆”上,值类型一定在“栈”上?

那引用类型在内存中的布局细节您又知道多少了?

.Net Framework 中的Types分类

C# type categorization. 带阴影的都是 C# 的内建类型关键字.

除了object and string(分别为System.Object 和System.String别名), 其他带阴影的都是简单的值类型.

下面是摘自《C#语言规范5.0》 –> 4.类型(page:77)

C# 语言的类型划分为两大类:值类型 (Value type) 和引用类型 (reference type)。

第三种类型是指针,只能用在不安全代码中。

引用类型和值类型在内存中如何分配的呢?

这一块我们将通过一小段代码来讲解,在讲解前让我们回顾下

引用类型 和值类型的赋值过程中在内存处理上的区别:

  • 把一个值类型a(定义如下int a=80;)赋给另外一个值类型b(int b;),即(b=a;)时,会把a的值拷贝一份给a,如下图;

  • 把一个引用类型a(定义如下Employee a=new employee();)赋给另外一个引用类型b(Employee b;),即(b=a;)时,会把a的地址(引用)拷贝一份给a,即他们指向同一个地址;

开始代码讲解,首先看代码如下:

Form myForm = new Form();
Size s = new Size (100, 100); // struct = value type
Font f = new Font (“Arial”,10); // class = reference type
myForm.Size = s;
myForm.Font = f;

注意代码中 myForm.Size中的Size和myForm.Font的Font是Form类型的属性(Property)不是类型(class type,代表某个类型)。

在.NetFramework中这样的使用方式极其普遍,初学者不要混淆了这两者。

讲解代码前给大家再提下知识点:

  • Size是Struct类型,当然就是值类型(ValueType)
  • Font是Class类型,当然就是引用类型(ReferenceType)

上面这段代码的内存中的分配,示意图:

很清楚地看到

  • Size类型的s分配到了Stack上,而Front类型的f 和Form类型的myForm则分配在堆上。
  • 并且myForm的Font属性引用到了Font类型f。
  • myForm的Size属性有它自己的值(Width和Height),它是Size类型s的一个拷贝。

这里我们更清晰的看到了值类型和引用类型在值赋值过程中的区别

我们可以通过修改Font类型f的值,来修改myForm中的字体样式,但不能通过修改s来修改myFrom的Size。

引用类型的Object内存布局基础结构

上面这个图展示的结构是通过分析源码得出的:

  1. 首先ObjectHeader(在其所在的AppDomain中的那个Thread通过调用Monitor.Enter锁了这个对象)
  2. 接下来是Method Table 指针(该指针指向AppDomain中声明(定义)托管类型),如果程序集被加载到AppDomain neutral 中,那么所有的AppDomain中该类型实例的Method Table指针都一样。CLR 类型系统的该基础构建块在托管代码中都是可视的。(TypeHandle.Value 是一个IntPtr
  3. 最后就是这部分就是该类型实例的值。

CLR object的这个实例对象的地址在垃圾回收时也有可能会发生变动。(具体参看GC中压缩过程)

\sscli20\clr\src\vm\object.h

//
// The generational GC requires that every object be at least 12 bytes
// in size.
#define MIN_OBJECT_SIZE (2*sizeof(BYTE*) + sizeof(ObjHeader))

A .NET object has basically this layout:

class Object
{
protected:
MethodTable* m_pMethTab; };
class ObjHeader
{
private:
// !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader.
DWORD m_SyncBlockValue; // the Index and the Bits
};
Platform 最小实例大小(bytes)
x86 12 bytes = 2*4+4
x64 24 bytes = 2*8+8

引用类型的Object内存布局代表性结构

普通对象

数组对象 - Array

字符串对象

Boxing,小心您的值类型不经意间被装箱

int a=1;
object b=a;

这段代码大家都知道会发生装箱,装箱后原来的值类型会有哪些变化?看下装箱和拆箱的步骤:

装箱:
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。

第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。

第二步:将值类型的实例字段拷贝到新分配的内存中。

第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。

有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。

拆箱:

检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。

有书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。

那么给你个自定义结构体,你还清楚什么情况会被装箱吗?

参考《防止装箱落实到底,只做一半也是失败

附加

为方便大家查看源码,这里提供一个源码索引表

SSCLI文件索引

Item SSCLI Path
AppDomain \sscli\clr\src\vm\appdomain.hpp
AppDomainStringLiteralMap \sscli\clr\src\vm\stringliteralmap.h
BaseDomain \sscli\clr\src\vm\appdomain.hpp
ClassLoader \sscli\clr\src\vm\clsload.hpp
EEClass \sscli\clr\src\vm\class.h
FieldDescs \sscli\clr\src\vm\field.h
GCHeap \sscli\clr\src\vm\gc.h
GlobalStringLiteralMap \sscli\clr\src\vm\stringliteralmap.h
HandleTable \sscli\clr\src\vm\handletable.h
InterfaceVTableMapMgr \sscli\clr\src\vm\appdomain.hpp
Large Object Heap \sscli\clr\src\vm\gc.h
LayoutKind \sscli\clr\src\bcl\system\runtime\interopservices\layoutkind.cs
LoaderHeaps \sscli\clr\src\inc\utilcode.h
MethodDescs \sscli\clr\src\vm\method.hpp
MethodTables \sscli\clr\src\vm\class.h
OBJECTREF \sscli\clr\src\vm\typehandle.h
SecurityContext \sscli\clr\src\vm\security.h
SecurityDescriptor \sscli\clr\src\vm\security.h
SharedDomain \sscli\clr\src\vm\appdomain.hpp
StructLayoutAttribute \sscli\clr\src\bcl\system\runtime\interopservices\attributes.cs
SyncTableEntry \sscli\clr\src\vm\syncblk.h
System namespace \sscli\clr\src\bcl\system
SystemDomain \sscli\clr\src\vm\appdomain.hpp
TypeHandle \sscli\clr\src\vm\typehandle.h

更多源码参考http://www.projky.com/dotnet

参考

The Truth About .NET Objects And Sharing Them Between AppDomains

Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing

Shared Source Common Language Infrastructure

[翻译经典文章]深入.NET Framework内部, 看看CLR如何创建运行时对象的

.NET对象的内存布局

托管堆与垃圾收集

C# 装箱和拆箱[整理]

mdsn类型详解(Jit and Run)

.NET 类型(Types)的那些事的更多相关文章

  1. JavaScript 类型判断的那些事

    先准备几个变量 var a = "abcde."; var b = 222; var c= [1,2,3]; // 或者 new Array() var d = new Date( ...

  2. Swift语法3.03(类型Types)

    类型 在Swift中,有两种类型:命名型类型和复合型类型.命名型类型是在定义时可以给定的特定名字的类型.命名型类型包括类,结构体,枚举和协议.例如,自定义的类MyClass的实例拥有类型MyClass ...

  3. 关于Mach-O类型文件那点事

    Mach-O文件简介   Mach-O是一种文件格式,是Mach Object文件格式的缩写. 它通常应用于可执行文件,目标代码,动态库,内核转储等中.   Mach-O作为大部分基于Mach核心的操 ...

  4. TYPES、DATA、TYPE、LIKE、CONSTANTS、STATICS、TABLES

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  5. Python中为什么推荐使用isinstance来进行类型判断?而不是type

    转自:http://www.xinxingzhao.com/blog/2016/05/23/python-type-vs-isinstance.html Python在定义变量的时候不用指明具体的的类 ...

  6. 重装eclipse要做的事

    当我们要在新环境上安装eclipse时,往往会做很多的个性修改和安装一些插件,下面就这些做一下总结: 一.插件 1.svn插件(subclipse) 插件官网下载地址:http://subclipse ...

  7. item 6: 当auto推导出一个不想要的类型时,使用显式类型初始化的语法

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 Item 5解释了比起显式指定类型,使用auto来 ...

  8. 【Python】Python—判断变量的基本类型

    type() >>> type(123)==type(456) True >>> type(123)==int True >>> type('ab ...

  9. C++中的void类型

    Technorati 标签: void,指针 1.1. void类型 void类型其实是一种用于语法性的类型,而不是数据类型,主要用于作为函数的参数或返回值,或者定义void指针,表示一种未知类型. ...

随机推荐

  1. 面积(area)

    题目描述 编程计算由"*"号围成的下列图形的面积.面积计算方法是统计*号所围成的闭合曲线中点的数目.如图所示,在10*10的二维数组中,“*”围住了15个点,因此面积为15. 0 ...

  2. 黑社会团伙(gangs)

    题目描述 众所周知,香港的黑社会组织猖獗,警方希望能摸清他们的内部构成情况,特派小生前往调查.经过长期的卧底,小生初步获得了一些资料:整个组织有 n 个人,任何两个认识的人不是朋友就是敌人. 而且满足 ...

  3. PHP流程控制之特殊结构

    在前两节介绍的循环结构中,都是通过循环语句本身提供的条件表达式来指定循环次数执行代码块直到停止循环.但如果想在循环体执行过程中中止循环,或是跳过一些循环继续执行其他代码块,我们就需要一些特殊的流程控制 ...

  4. 【算法】PHP实现冒泡排序和快速排序--防遗忘

    有没有这样的感觉,排序算法虽然简单,但是没看过一次,一会就又忘了,所以有必要 自己使用实际的代码运行实现,才记忆牢固,为此Mark //需求:将数组中元素,从大到小排列$a = array(11, 2 ...

  5. JavaScript利用装饰模拟实现私有状态

    在经典的面向对象编程中,经常需要将对象的某个状态封装或隐藏在对象内,只有通过对象的一幅幅和能访问这些状态,对外只暴露一些重要的状态变量可以直接读写. 我们可以通过将变量(或参数)装饰在一个构造函数内来 ...

  6. [注意]SerialPort操作PCI-1621D多串口卡,出现异常"参数不正确"

    开发LED大屏显示.40-20mA模拟量输出的时候,经常要与串口打交道.但是Windows自带的SerialPort串口操作组件貌似兼容性 不是太好,或是SerialPort本身有BUG,在操作PCI ...

  7. IOS应用内存释放机制

    这是由iOS系统管理决定的,但APP退出在后台后,只有10秒的持续运行时间,然后暂停.但该APP还在内存中,当出现内存警告,也就是别的APP要运行,而此时内存又不足的情况下,系统会回收停在后台APP所 ...

  8. Xdebug文档(二)变量显示特性

    Xdebug能替代PHP的var_dump()函数来显示变量值.xdebug的版本包含对不同数据类型作数组元素/对象属性.最大深度和字符串长度以不同颜色标识.这里也有一些功能能很好地处理变量显示. 相 ...

  9. Java基础学习 -- 异常

    当异常发生时,原本要接着执行的代码不再执行,转而让其他部分的代码来处理.如果没有代码负责处理,控制台会报告异常. 异常出现时的执行机制: 异常机制最大的好处是:清晰地分开了 正常的业务逻辑 和 遇到情 ...

  10. 深入理解CSS盒子模型

    在CSS中浮动.定位和盒子模型,都是很核心的东西,其中盒子模型是CSS很重要基石之一,感觉还是很有必要把CSS盒子模型相关知识更新一下...... CSS盒子模型<BoxModel>示意图 ...