將源程序編譯成托管模塊
公共語言運(yùn)行時(shí)(CLR)
概念:CLR是一個(gè)可由多種編程語言使用的“運(yùn)行時(shí)”。CLR的核心功能(如內(nèi)存管理、程序集加載、安全性、異常處理和線程同步)可由面向CLR的所有語言使用。如“運(yùn)行時(shí)”使用異常來報(bào)告錯誤,面向它的語言都能通過異常來報(bào)告錯誤。
特性:CLR不關(guān)心開發(fā)人員使用的哪種語言,只需要編譯器時(shí)面向CLR的(符合CLR標(biāo)準(zhǔn))??捎弥С諧LR的任何語言創(chuàng)建源代碼,然后使用對應(yīng)的編譯器檢查語法和分析源代碼。

標(biāo)準(zhǔn):無論使用哪個(gè)編輯器,結(jié)果都是托管模塊,托管模塊的標(biāo)準(zhǔn)是32位Microsoft Windows可移植執(zhí)行體(PE32)文件,或者是標(biāo)準(zhǔn)的64位windows可移植執(zhí)行體(PE32+)文件,它們都需要CLR才能執(zhí)行。

IL
本機(jī)代碼編譯器生成的都是面向特定CPU架構(gòu)的代碼。面向CLR的編譯器生成都是IL(中間語言)代碼。IL代碼有時(shí)稱為托管代碼,因?yàn)镃LR管理它的執(zhí)行。
元數(shù)據(jù)(metadata)
概念:元數(shù)據(jù)是一個(gè)數(shù)據(jù)表集合,一些數(shù)據(jù)表描述了模塊中定義了那些內(nèi)容(比如類型和成員等),另一些描述了模塊引用了什么(比如導(dǎo)入的類型及成員)。
用途:元數(shù)據(jù)總是嵌在和代碼相同的EXE/DLL文件中,編譯器同時(shí)生成元數(shù)據(jù)和代碼,把它們綁定在一起并生成最終的托管模塊。
元數(shù)據(jù)避免了編譯對原生C/C++頭和庫文件的需求,因?yàn)樵趯?shí)現(xiàn)類型/成員的IL代碼中,包含有關(guān)引用類型/成員全部信息。編譯器直接從托管模塊讀取元數(shù)據(jù)。
VS使用元數(shù)據(jù)來實(shí)現(xiàn)代碼提示
CLR進(jìn)行驗(yàn)證的過程會使用元數(shù)據(jù)確保類型安全
元數(shù)據(jù)運(yùn)行將對象的字段序列化到內(nèi)存,并到其他機(jī)器反序列化完成對象重建狀態(tài)。
元數(shù)據(jù)允許垃圾回收器跟蹤對象生存期。垃圾回收器能判斷任何對象的類型,并從元數(shù)據(jù)中知道對于引用了那些其他的對象。
1.2 將托管程序合并成程序集
背景:CLR實(shí)際上不和模塊工作。它和程序集工作。程序集(assembly)是一個(gè)或多個(gè)模塊/資源文件的邏輯性分組。其次,程序集是重用、安全性以及版本控制的最小單元。在CLR中,程序集相當(dāng)于“組件”。編譯器默認(rèn)將生成的托管模塊轉(zhuǎn)換成程序集。

1.3 加載公共語言運(yùn)行時(shí)
生成的程序集既可以是可執(zhí)行應(yīng)用程序,也可以是DLL。最終都是由CLR管理這些代碼的執(zhí)行。所以目標(biāo)機(jī)器必須安裝.Net Framework。根據(jù)我們程序集編譯的platform(32位/64位/ARM)不一樣,.Net會加載不同版本MSCorEE.dll,進(jìn)而進(jìn)程的主線程調(diào)用MSCorEE.dll的方法初始化CLR,加載EXE程序集,在調(diào)用其Main方法。應(yīng)用程序啟動并運(yùn)行。

1.4 執(zhí)行程序集的代碼
JIT 編譯
為了執(zhí)行方法,首先必須把方法的IL轉(zhuǎn)換位本機(jī)CPU指令,這是CLR JIT(just-in-time “即時(shí)”)編譯器的職責(zé)。
方法首次調(diào)用的時(shí)候,JITCompiler函數(shù)會被調(diào)用。JITCompiler函數(shù)負(fù)責(zé)將方法IL代碼編譯成CPU指令。JITCompiler會在定義(該類型的)程序集的元數(shù)據(jù)中查找被調(diào)用方法的IL,接著JITCompiler驗(yàn)證IL代碼,并將IL代碼編譯成本地CPU指令。本地CPU指令分配到動態(tài)的內(nèi)存塊中。然后JITCompiler回到CLR為類型創(chuàng)建的內(nèi)部數(shù)據(jù)結(jié)構(gòu),找到被調(diào)用方法對于的那條記錄,修改對JITCompiler的引用,使其執(zhí)行內(nèi)存塊(剛剛編譯好的)的地址。最后JITCompiler函數(shù)跳轉(zhuǎn)到內(nèi)存塊的代碼,執(zhí)行。

方法二次調(diào)用的時(shí)候,完全跳過JITCompiler函數(shù),直接執(zhí)行本機(jī)代碼

JIT編譯將本地CPU指令存儲在動態(tài)內(nèi)存中。程序一旦中止,編譯好的代碼也會被丟棄。再次運(yùn)行或應(yīng)用程序啟動多個(gè)實(shí)例,JIT都必須再次將IL編譯為本機(jī)代碼。對于某些引用程序,可能會顯著增加內(nèi)存。相比之下,本機(jī)應(yīng)用程序的只讀代碼頁可由應(yīng)用程序運(yùn)行的所有實(shí)例共享。
編譯指令
CLR的JIT編譯器會對本機(jī)代碼進(jìn)行優(yōu)化,類似于非托管C++編譯器后端所做的事情?;ㄙM(fèi)較多時(shí)間生成優(yōu)化代碼,提高性能。

/optimize-:在C#編譯器生成為優(yōu)化的IL代碼中,包含許多NOP(no-operation,空操作)指令。還包含很多跳轉(zhuǎn)下一行代碼的分支指令,主要是用于VS加斷點(diǎn)調(diào)試。
/debug(+/full/pdbonly):編譯器會生成Program Database(PDB)文件,PDB文件幫助調(diào)試器查找局部變量并將IL指令映射到源代碼。/debug:full告訴JIT編譯器你打算調(diào)試程序集,JIT編譯器會記錄每條IL指令所生成的本機(jī)代碼,這樣使用VS的時(shí)候就可以對對應(yīng)進(jìn)程源代碼進(jìn)行調(diào)試。
新建C#項(xiàng)目時(shí),項(xiàng)目Debug配置的時(shí)/optimize-和/debug:full,而Release配置的是/optimize+和debug:pdbonly
JIT優(yōu)化
JIT編譯編譯器在運(yùn)行時(shí)將IL代碼編譯成本機(jī)代碼時(shí),編譯器對執(zhí)行環(huán)境的認(rèn)識比非托管編譯器更深刻,下面列舉托管代碼相較于非托管代碼的優(yōu)勢。
JIT編譯器能判斷應(yīng)用運(yùn)行的CPU,并生成對于CPU支持的特殊指令,相反,非托管應(yīng)用程序通常時(shí)針對最小功能集合的CPU編譯的,不會使用提升性能的特殊指令
JIT編譯器能預(yù)判一個(gè)方法在它對應(yīng)機(jī)器上總是測試失敗。如主機(jī)只有一個(gè)CPU,JIT編譯器不會執(zhí)行需要多個(gè)cpu支持的IL代碼生成CPU指令,從而減少代碼,執(zhí)行更快。
應(yīng)用程序運(yùn)行時(shí),CLR可以評估代碼的執(zhí)行,并將IL重新編譯成本機(jī)代碼,減少不正確的分支預(yù)測。雖然目前的版本不支持。。。
預(yù)編譯
.Net Framewrok SDK配套的NGen.exe工具將程序集的所有IL代碼編譯成本機(jī)代碼,并將這些代碼保存到一個(gè)磁盤文件中。在運(yùn)行程序集時(shí),CLR自動判斷是否存在該程序集的預(yù)編譯版本。如果有,CLR加載預(yù)編譯版本,這樣一來就避免了運(yùn)行時(shí)編譯。但NGen.exe生成的代碼時(shí)很保存的,不想JIT編譯器生成代碼那么高度的優(yōu)化
1.4.1 IL和驗(yàn)證
IL編譯成本地CPU指令時(shí),CLR執(zhí)行一個(gè)名為驗(yàn)證的過程。這個(gè)過程會檢查高級IL代碼,確定代碼所作的一切都是安全的。比如,核實(shí)調(diào)用的每個(gè)方法都有正確數(shù)量以及類型的參數(shù),每個(gè)方法都有一個(gè)返回語句等。托管模塊的元數(shù)據(jù)包含驗(yàn)證中所用到的方法及類型信息。
Windows的每個(gè)進(jìn)程都有自己的虛擬地址空間,獨(dú)立地址空間之所以必要是因?yàn)椴荒芎唵蔚男湃我粋€(gè)應(yīng)用程序的代碼。應(yīng)用程序完全可以讀寫無效的內(nèi)存地址。所有每個(gè)進(jìn)程都放大獨(dú)立的地址空間,互不干擾,保證程序健壯性與穩(wěn)定性。通過驗(yàn)證托管代碼,可以保證代碼不會不正確的訪問內(nèi)存,不會干擾到另一個(gè)應(yīng)用程序的代碼。
健壯性與可靠性:可靠性主要描述系統(tǒng)的正確性,也就是你提供一個(gè)參數(shù),他能殘生穩(wěn)定可預(yù)測的數(shù)據(jù)。但是如果你程序員執(zhí)行完成后,沒有正確釋放內(nèi)存,或者說系統(tǒng)沒有自動幫它釋放占用的資源,就認(rèn)為這個(gè)程序“運(yùn)行時(shí)”不健壯
1.4.2 不安全的代碼
定義:C#編譯器默認(rèn)生成安全代碼,這種代碼安全性可以驗(yàn)證,然而C#編譯器也運(yùn)行寫不安全代碼,不安全代碼允許直接操作內(nèi)存地址,并可操作這些地址處的字節(jié),通常只有在與非托管代碼進(jìn)行交互或提升對效率要求極高的一個(gè)算法性能的時(shí)候,才這樣做。
風(fēng)險(xiǎn):這種代碼可能破壞數(shù)據(jù)結(jié)構(gòu),危害安全性,甚至造成新的安全漏洞,所以C#編譯器要求包含不安全代碼的所有方法都使用unsafe關(guān)鍵字標(biāo)記。此外 C#編譯器要求使用/unsafe編譯器開關(guān)來編譯源代碼。
1.7 通用類型系統(tǒng)
CTS(通用類型系統(tǒng)):Microsoft制定的一個(gè)正式的規(guī)范來描述類型的定義和行為。
- 字段(Field)
作為對象狀態(tài)一部分的數(shù)據(jù)變量,用名稱和類型來區(qū)分 - 方法(Method)
針對對象執(zhí)行操作的函數(shù),通常會改變對象狀態(tài)。方法有一個(gè)名稱、一個(gè)簽名、一個(gè)或多個(gè)修飾符。簽名制定參數(shù)數(shù)量及順序、類型、方法是否有返回值,如果有返回值還需要返回值類型。 - 屬性(Property)
屬性運(yùn)行在訪問值之前校驗(yàn)輸入?yún)?shù)和對象的狀態(tài),以及在必要時(shí)在計(jì)算某個(gè)值(getter、setter) - 事件(Event)
事件在對象以及其他相關(guān)對象之間實(shí)現(xiàn)通知機(jī)制。
訪問權(quán)限:
- private
成員同類訪問 - family
成員可由派生類訪問 - family and assembly
成員和由派生類訪問且必須在同一程序集 - assembly
成員同一程序集訪問 - family or assembly
成員可由任何程序集中派生類訪問 - public
成員可任意程序集中任何代碼訪問
簡單總結(jié)今天學(xué)到的重要內(nèi)容:
