.NET 类型(Types)的那些事 – 霍旭东

引言

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

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

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

本篇就将这些问题一一道来

 

.Net Framework 中的Types分类

 

09fig01

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,如下图;

value_type

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

reference_type1

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

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)

 

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

image006

很清楚地看到

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

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

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

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

image_thumb_2

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

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

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

 

sscli20clrsrcvmobject.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

 

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

SSCLI文件索引

Item SSCLI Path
AppDomain sscliclrsrcvmappdomain.hpp
AppDomainStringLiteralMap sscliclrsrcvmstringliteralmap.h
BaseDomain sscliclrsrcvmappdomain.hpp
ClassLoader sscliclrsrcvmclsload.hpp
EEClass sscliclrsrcvmclass.h
FieldDescs sscliclrsrcvmfield.h
GCHeap sscliclrsrcvmgc.h
GlobalStringLiteralMap sscliclrsrcvmstringliteralmap.h
HandleTable sscliclrsrcvmhandletable.h
InterfaceVTableMapMgr sscliclrsrcvmappdomain.hpp
Large Object Heap sscliclrsrcvmgc.h
LayoutKind sscliclrsrcbclsystemruntimeinteropserviceslayoutkind.cs
LoaderHeaps sscliclrsrcincutilcode.h
MethodDescs sscliclrsrcvmmethod.hpp
MethodTables sscliclrsrcvmclass.h
OBJECTREF sscliclrsrcvmtypehandle.h
SecurityContext sscliclrsrcvmsecurity.h
SecurityDescriptor sscliclrsrcvmsecurity.h
SharedDomain sscliclrsrcvmappdomain.hpp
StructLayoutAttribute sscliclrsrcbclsystemruntimeinteropservicesattributes.cs
SyncTableEntry sscliclrsrcvmsyncblk.h
System namespace sscliclrsrcbclsystem
SystemDomain sscliclrsrcvmappdomain.hpp
TypeHandle sscliclrsrcvmtypehandle.h

 

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

 

普通对象

 

012302077246530

数组对象 – Array

012302218801605

字符串对象

012302322553069

 

参考

 

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对象的内存布局

托管堆与垃圾收集

本文链接:.NET 类型(Types)的那些事,转载请注明。



You must enable javascript to see captcha here!

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress

无觅相关文章插件,快速提升流量