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

     

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

    普通对象

    012302077246530

    数组对象 - Array

    012302218801605

    字符串对象

    012302322553069

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

    int a=1;
    object b=a;

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

    装箱:
    对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。
    第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
    第二步:将值类型的实例字段拷贝到新分配的内存中。
    第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
    有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。
    拆箱:
    检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。
    有书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。

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

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

    附加

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

    SSCLI文件索引

    ItemSSCLI 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 sscliclrsrcclsystem untimeinteropserviceslayoutkind.cs
    LoaderHeaps sscliclrsrcincutilcode.h
    MethodDescs sscliclrsrcvmmethod.hpp
    MethodTables sscliclrsrcvmclass.h
    OBJECTREF sscliclrsrcvm ypehandle.h
    SecurityContext sscliclrsrcvmsecurity.h
    SecurityDescriptor sscliclrsrcvmsecurity.h
    SharedDomain sscliclrsrcvmappdomain.hpp
    StructLayoutAttribute sscliclrsrcclsystem untimeinteropservicesattributes.cs
    SyncTableEntry sscliclrsrcvmsyncblk.h
    System namespace sscliclrsrcclsystem
    SystemDomain sscliclrsrcvmappdomain.hpp
    TypeHandle sscliclrsrcvm ypehandle.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)

  • 相关阅读:
    java+selenium自动化-IE浏览器搭建自动化环境
    python中的opencv
    随机森林参数说明
    剑指offer
    Python中常用的包--sklearn
    Anaconda安装,jupyter notebook 使用说明
    C++中的Public 、Private、Protected 区别
    C++类中的Static关键字二
    C++类中的Static关键字
    c语言二级指针内存模型
  • 原文地址:https://www.cnblogs.com/HQFZ/p/4638794.html
Copyright © 2020-2023  润新知