C#編譯和運行原理


 

關於編譯與內存的關系,以及執行時內存的划分

1、所謂在編譯期間分配空間指的是靜態分配空間(相對於用new動態申請空間),如全局變量或靜態變量(包括一些復雜類型的

常量),它們所需要的空間大小可以明確計算出來,並且不會再改變,因此它們可以直接存放在可執行文件的特定的節里(而且

包含初始化的值),程序運行時也是直接將這個節加載到特定的段中,不必在程序運行期間用額外的代碼來產生這些變量。

其實在運行期間再看“變量”這個概念就不再具備編譯期間那么多的屬性了(諸如名稱,類型,作用域,生存期等等),對應的

只是一塊內存(只有首址和大小), 所以在運行期間動態申請的空間,是需要額外的代碼維護,以確保不同變量不會混用內存。

比如寫new表示有一塊內存已經被占用了,其它變量就不能再用它了; 寫delete表示這塊內存自由了,可以被其它變量使用了。

(通常我們都是通過變量來使用內存的,就編碼而言變量是給內存塊起了個名字,用以區分彼此)

內存申請和釋放時機很重要,過早會丟失數據,過遲會耗費內存。特定情況下編譯器可以幫我們完成這項復雜的工作(增加額外

的代碼維護內存空間,實現申請和釋放)。從這個意義上講,局部自動變量也是由編譯器負責分配空間的。進一步講,內存管理

用到了我們常常掛在嘴邊的堆和棧這兩種數據結構

 

  最后對於“編譯器分配空間”這種不嚴謹的說法,你可以理解成編譯期間它為你規划好了這些變量的內存使用方案,這個方案寫

到可執行文件里面了(該文件中包含若干並非出自你大腦衍生的代碼),直到程序運行時才真正拿出來執行。

 

2、編譯其實只是一個掃描過程,進行詞法語法檢查,代碼優化而已。我想你說的“編譯時分配內存”是指“編譯時賦初值”,它只是形成一個文本,檢查無錯誤,並沒有分配內存空間。

當你運行時,系統才把程序導入內存。一個進程(即運行中的程序)在主要包括以下五個分區:
棧區、堆區、全局數據區/靜態區、代碼區、常量區 

  • 棧區用來存放局部數據或者是函數的參數,函數的返回值之類的變量(其中還有返回到調用函數下一條指令的地址)
  • 堆區用來存放程序中動態申請內存的變量
  • 全局變量/靜態區用來存放程序中的全局變量或者是靜態變量,因為它們的大小是確定的,在編譯期間就已經進行靜態空間的分配,而且不會改變,這樣會提高程序對這些數據的訪問速度
  • 代碼區(code)用來存放編譯后的二進制代碼
  • 常量區用來存放我們聲明的常量(const類型)

代碼(編譯后的二進制代碼)放在code區,代碼中生成的各種變量、常量按不同類型分別存放在其它四個區。系統依照代碼順序

執行,然后依照代碼方案改變或調用數據,這就是一個程序的運行過程。

3、

編譯時分配內存

---------------
編譯時是不分配內存的。此時只是根據聲明時的類型進行占位,到以后程序執行時分配內存才會正確。所以聲明是給編譯器看的

,聰明的編譯器能根據聲明幫你識別錯誤。

運行時分配內存
---------------
這是對的,運行時程序是必須調到“內存”的。因為CPU(其中有多個寄存器)只與內存打交道的。程序在進入實際內存之前要首

先分配物理內存。

編譯過程
---------------
當執行這個EXE文件以后,此程序就被加載到內存中,成為進程。此時一開始程序會初始化一些全局對象,然后找到入口函數

,就開始按程序的執行語句開始執行。此時需要的內存只能在程序的堆上進行動態增加/釋放了

 

編譯過程

編譯過程:是把源文件翻譯成目標文件存到硬盤上,這個過程占不占內存沒關系,是編譯器做的事,目標文件由三個部分組成:

1. 文件信息,包括文件類型,文件大小等等,如:DLL文件的前兩個字節是0x4d 0x5a。

2. 代碼,就是程序,如 HData data = new HData(); HData是自己定義的類,這一句被轉換成如下形式,共占37個字節。
00000040 B9 10 7F DA 00 mov ecx,0DA7F10h  
00000045 E8 E2 4D D4 FB call FBD44E2C  
0000004a 89 45 B4 mov dword ptr [ebp-4Ch],eax  
0000004d 8B 4D B4 mov ecx,dword ptr [ebp-4Ch]  
00000050 E8 0B F0 AB FB call FBABF060  
00000055 8B 55 C4 mov edx,dword ptr [ebp-3Ch]  
00000058 8B 45 B4 mov eax,dword ptr [ebp-4Ch]  
0000005b 8D 92 84 01 00 00 lea edx,[edx+00000184h]  
00000061 E8 3A 5B FE 74 call 74FE5BA0

3. 數據,包括全局變量、靜態變量和常量。類成員變量、方法局部變量不編譯到文件中。如:static int a = 0; 在文件中占四個字節,int a = 1, 在文件中不占字節,string str = "12345", 雖然是類成員,但其中隱含常量“12345”,在文件中占5個字節 。

運行過程:
1. 雙擊圖標時,系統把exe文件全部調入內存,主要包括所有程序和全局變量,這一部分內存一直被占用到退出程序。

2. 運行程序,還以這一句為例,HData data = new HData(); HData類的程序已經在內存中,所有HData類的實例共用一套程序,系統只是為HData的數據(主要是HData中的變量)分配一塊內存,並把這塊內存的起點,靜態變量除外,它在加載exe文件的時候調入內存。data 失效的時候,這一塊內存被釋放。
  局部變量,void aaaa(){ int a = 1; } 這段程序的主體部分大約占5個字節,a變量不占內存,調用aaaa()的時候執行一行匯編碼 add bp, 4 就是在棧中分配四個字節給a,aaaa()返回的時候,a變量占用的四個字節被釋放。

3. 退出應該程序的時候釋放exe文件占用的內存。

以上只是一個大至的原理,實際情況要復雜的多,象分配的內存是可移動的,甚至會被放到內存中。不過咱們做應用程序的了解這些就足夠了。再深入是的做系統和做編譯器的人管的事。

寫完以后才發現是07年的帖子,哈,還是發了吧。
本文摘抄自網絡。
 
第二篇
 
c語言的運行編譯
  一段printf代碼到計算機運行輸出的過程。編譯器即中介,把代碼翻譯成機器認識的0與1的編碼。編譯器與硬件緊密相連。
.net編譯
  微軟推行.NET平台的同時,推出了強大的Framework. 在XP系統的時候需要自行安裝Framework, 在Win7系統中已經自動集成了。Framework的強大,在於面向對象的基礎類庫與運行時CLR. 基礎類庫即編程時可以使用的工具類,這里不多說,着重說一下CLR.運行時是程序運行的監管者,程序運行起來運行的怎么樣了,是否對內存有太大的消耗,是否對內存進行整理清空,出現異常怎么進行處理,在運行時中都有相應的對策。
 
  這里的編譯器與上邊的編譯器不太一樣。在編譯的過程中,並沒有編譯成CPU認識的編碼,而是CLR認識的編碼。在編譯的過程中,把框架庫中的東西加進來了。編譯完成結果是exe文件,但是這個exe文件是IL文件,是不能被CPU識別的,只能被CLR識別。雙擊這個文件,CLR會把其加載到內存中,這時要出場的就是及時編譯器JIT. 及時編譯器的作用就是識別IL文件,然后操作CPU去完成相應的操作。即由運行時將exe轉換成CPU認識的0與1編碼,操作計算機。
 
  這里涉及到了一個概念是多語言平台的混合編程。IL中間文件是一種標准,一種規范。只要符合這種規范的文件,都能被CLR識別運行。C#,F#,VB,編寫的代碼,只要提供相應的編譯器,編譯出來的東西都能被CLR識別運行。而且這里為跨平台做了很好的准備。因為IL的規范是確定了的,對於C#程序的移植,比如說移植到Linux上,我們只需要有一個能在Linux上運行的CLR就行了,這個CLR能識別在Windows上編譯好的IL運行。Mono就是很好的一個例子。
 
 
  .Net的運行效率其實完全決定於JIT. 將中間語言代碼根據當前的硬件與軟件環境,進行運行時編譯,並緩存代碼。及時編譯器會根據 操作系統與操作系統與硬件環境對代碼優化。CLR是托管代碼,加了中間層,為什么效率還高?這是JIT決定的。根據硬件平台來編譯,而不是每一句代碼都編譯。例如空的for循環是不進行編譯,提高效率。如果代碼中有很多次調用一個方法,那么CLR在JIT編譯這個代碼第一次以后就會將已經編譯好的代碼緩存起來,下一次在使用的時候,不去編譯了,直接從緩存中取出編譯好的代碼執行即可。還有垃圾回收機制,將不用的或者不常用的代碼刪除掉,如果再要使用,重新創建。整理內存,使得內存連續。
 
本文摘抄自網絡。
 
 第三篇
  純C/C++的程序通常運行在一個非托管環境中,類是由頭文件(.h)和實現文件(.cpp)組成,每個類形成了一個單獨的編譯單元,當我們編譯程序時,幾個基本組件會把我們的源代碼翻譯成二進制代碼。

  首先是預處理器,如果在項目中有頭文件和宏表達式,那么它將負責包含頭文件和翻譯所有的宏觀表達式。

接下來是編譯器,它不是直接生成二進制代碼,而是生成匯編代碼(.s),這基本上是所有現代的非結構化語言的共同基礎。

然后,匯編程序把匯編代碼翻譯成目標代碼(.o和.obj文件,機器指令)。

最后鏈接器,它把所有彼此相關的目標文件和生成的可執行文件或庫鏈接起來。

總而言之,在一般情況下,我們的代碼首先翻譯成匯編代碼,接着翻譯成機器指令(二進制代碼)。

托管環境的編譯過程(C#/Java)

  在托管環境中,編譯的過程略有不同,我們熟知的托管語言有C#和Java,接下來,我們將以C#和Java為例介紹在托管環境中的編譯過程。

當我們在喜愛的IDE中編寫代碼時,第一個檢測我們代碼的就是IDE(詞法分析),然后,編譯成目標文件和鏈接到動態/靜態庫或可執行文件進行再次檢查(語法分析),最后一次檢查是運行時檢查。托管環境的共同特點是:編譯器不直接編譯成機器碼,而是中間代碼,在.NET中稱為MSIL - Microsoft Intermediate Language,Java是字節碼(Bytecode)

在那之后,在運行時JIT(Just In Time)編譯器將MSIL翻譯成機器碼,這意味着我們的代碼在真正使用的時候才被解析,這允許在CLR(公共語言運行時)預編譯和優化我們的代碼,實現程序性能的提高,但增加了程序的啟動時間,我們也可以使用Ngen(Native Image Generator)預編譯我們的程序,從而縮短程序的啟動時間,但沒有運行時優化的優點。(JeffWong的補充Java是先通過編譯器編譯成Bytecode,然后在運行時通過解釋器將Bytecode解釋成機器碼;C#是先通過編譯器將C#代碼編譯成IL,然后通過CLR將IL編譯成機器代碼。所以嚴格來說Java是一種先編譯后解釋的語言,而C#是一門純編譯語言,且需要編譯兩次。)

 

  .Net Framework就是在Win32 core上添加了一個抽象層,它提供的一個好處就是支持多語言、JIT優化、自動內存管理和改進安全性;另外一個完整解決方案是WinRT,但這涉及到另外一個主題了,這里不作詳細介紹。

JIT編譯的優點和缺點

JIT編譯帶來了許多好處,最大的一個在我看來是性能的優勢,它允許CLR(通用語言運行時扮演Assembler組件)只執行需要的代碼,例如:假設我們有一個非常大的WPF應用程序,它不是立即加載整個程序,而是CLR開始執行時,我們代碼的不同部分將通過一個高效的方法翻譯成本地指令,因為它能夠檢查系統JIT和生成優化的代碼,而不是按照一個預定義的模式。不幸的是,有一個缺點就是啟動的過程比較慢,這意味着它不適用於加載時間長的包。

JIT的替代方案使用NGen

如果Visual Studio由JIT創建,那么它的啟動我們將需要等待幾分鍾,相反,如果它是使用Ngen(Native Image Generator)編譯,它將創建純二進制可執行文件,如果只考慮速度的問題,那是絕對是正確的選擇。

在非托管環境中,我們需要知道編譯的過程分成編譯和連接兩個階段,編譯階段將源程序(*.c,*.cpp或*.h)轉換成為目標代碼(*.o或*.obj文件),至於具體過程就是上面說的C/C++編譯過程的前三個階段;鏈接階段是把前面轉成成的目標代碼(obj文件)與我們程序里面調用的庫函數對應的代碼鏈接起來形成對應的可執行文件(exe文件)。

托管環境中,編譯過程可以分為:詞法分析、語法分析、中間代碼生成、代碼優化和目標代碼生成等等過程;無論是.NET還是Java,它們都會生成中間代碼(MSIL或Bytecode),然后把優化后的中間代碼翻譯成目標代碼,最后在程序運行時,JIT將IL翻譯成機器碼。

無論是托管或非托管語言,它們的編譯編譯過程是把高級語言翻譯成計算機能理解的機器碼,由於編譯過程涉及的知識面很廣(編譯的原理和硬件知識),而且本人的能力有限,也只能簡單的描述一下這些過程,如果大家希望深入了解編譯的原理,我推薦大家看一下《編譯原理》。

參考

[1] http://www.developingthefuture.net/compilation-process-and-jit-compiler/

更新:07/31/2013

本文作者:JK_Rush

原文地址:http://www.cnblogs.com/rush/ 

 

本文摘抄自博客園

 

 

 

 

 

 
 
 
 
 
 

 


免責聲明!

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



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