.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中壓縮過程)

 

\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內存布局代表性結構

 

普通對象

 

012302077246530

數組對象 - Array

012302218801605

字符串對象

012302322553069

 

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM