引言
您是.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內存布局基礎結構
上面這個圖展示的結構是通過分析源碼得出的:
- 首先ObjectHeader(在其所在的AppDomain中的那個Thread通過調用Monitor.Enter鎖了這個對象)
- 接下來是Method Table 指針(該指針指向AppDomain中聲明(定義)托管類型),如果程序集被加載到AppDomain neutral 中,那么所有的AppDomain中該類型實例的Method Table指針都一樣。CLR 類型系統的該基礎構建塊在托管代碼中都是可視的。(TypeHandle.Value 是一個IntPtr)
- 最后就是這部分就是該類型實例的值。
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如何創建運行時對象的
mdsn 類型詳解(Jit and Run)