C C++ Java C# JS編譯、執行過程的原理入門分析

C、C++是典型的編譯型編程語言,編譯連接後,點擊則可執行。
JS,解釋型腳本語言,則不須要進行編譯,直接解釋執行。
Java和C#則是所謂的高級語言,編譯執行的方式作了不少處理,
尤爲是C#,VS編譯後生成的exe文件並不是機器碼,讓不少程序員誤解。
 
下文筆者將本身的理解和查閱的資料,和你們分享。有疏漏之處,請你們指出,共同窗習。
 
一、C、C++
C、C++是典型的編譯型編程語言,編譯連接後,點擊則可執行。
C、C++ 編譯執行的原理,也比較簡單,直接是源碼通過編譯連接以後,生成機器碼組成的可執行文件,直接由OS進行Load加載執行。機器碼對應硬件的指令,因此,能夠直接點擊執行。
 
二、JS
JS,解釋型腳本語言,則不須要進行編譯,直接解釋執行。
 
三、Java
.java->編譯->.class,Java源碼通過編譯後,生成.class文件,.class須要jvm解釋執行,有部分.class文件也會經過JIT技術來直接生成機器語言,以提升執行效率。
Java這個語言很特殊。
你能夠說它是編譯型的。由於全部的Java代碼都是要編譯的,.java不通過編譯就什麼用都沒有。
你能夠說它是解釋型的。由於java代碼編譯後不能直接運行,它是解釋運行在JVM上的,因此它是解釋運行的,那也就算是解釋的了。
如今的JVM爲了效率,都有一些JIT優化。它又會把.class的二進制代碼編譯爲本地的代碼直接運行,因此,又是編譯的。
 
四、C#
C#的編譯執行過程以下:
(1)C# => C#編譯器 => IL(中間代碼)
(2)IL => JIT => Native Code(目標程序)
(3)輸入 => 目標程序 => 輸出。
C#一共編譯了兩次,第二次發生在運行時,也就是你點擊exe文件以後。
 
以HelloWorld程序爲例,來講明exe文件的本質,和運行原理:
 
HelloWorld.cs
//HelloWorld.cs by Cornfield,2001
//csc HelloWorld.cs
using System;
class HelloWorld
{
   public static void Main()
   {
     Console.WriteLine("Hello World !");
   }
}

 

須要指出的是,咱們通常使用C#編寫生成一個HelloWorld的exe文件,其實,內部存放的並非機器能夠解讀的機器碼,不要被後綴名exe欺騙了。java

 

 編譯輸出的HelloWorld.exe是一個由中間語言(IL),元數據(Metadata)和一個額外的被編譯器添加的目標平臺的標準可執行文件頭(好比Win32平臺就是加了一個標準Win32可執行文件頭)組成的PE(portable executable,可移植執行體)文件,而不是傳統的二進制可執行文件--雖然他們有着相同的擴展名。
 
中間語言是一組獨立於CPU的指令集,它可以被即時編譯器Jitter翻譯成目標平臺的本地代碼。中間語言代碼使得全部Microsoft.NET平臺的高級語言C#,VB.NET,VC.NET等得以平臺獨立,以及語言之間實現互操做。元數據是一個內嵌於PE文件的表的集合。元數據描述了代碼中的數據類型等一些通用語言運行時(Common Language Runtime)須要在代碼執行時知道的信息。元數據使得.NET應用程序代碼具有自描述特性,提供了類型安全保障,這在之前須要額外的類型庫或接口定義語言(Interface Definition Language,簡稱IDL)。 

這樣的解釋可能仍是有點讓人困惑,那麼咱們來實際的解剖一下這個PE文件。咱們採用的工具是.NET SDK Beta2自帶的ildasm.exe,它能夠幫助咱們提取PE文件中的有關數據。咱們鍵入命令"ildasm /output:HelloWorld.il HelloWorld.exe",通常能夠獲得兩個輸出文件:helloworld.il和helloworld.res。其中後者是提取的資源文件,咱們暫且無論,咱們來看helloworld.il文件。咱們用"記事本"程序打開能夠看到元數據和中間語言(IL)代碼,因爲篇幅關係,咱們只將其中的中間語言代碼提取出來列於下面,有關元數據的表項咱們暫且不談:
 
//  Microsoft (R) .NET Framework IL Disassembler.  Version 1.1.4322.573
//  Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 1:0:5000:0
}
.assembly Class2
{
  // --- 下列自定義屬性會自動添加,不要取消註釋 -------
  //  .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
  //                                                                                bool) = ( 01 00 00 01 00 00 ) 
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module Class2.exe
// MVID: {A9D4A2DC-A401-4F5F-B16F-B3D40F584E59}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x070c0000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.class public auto ansi beforefieldinit Test
       extends [mscorlib]System.Object
{
} // end of class Test

// =============================================================
// =============== GLOBAL FIELDS AND METHODS ===================
// =============================================================
// =============== CLASS MEMBERS DECLARATION ===================
//   note that class flags, 'extends' and 'implements' clauses
//          are provided here for information only

.class public auto ansi beforefieldinit Test
       extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main() cil managed
  {
    .entrypoint
    // 代碼大小       72 (0x48)
    .maxstack  4
    .locals init (int32[] V_0,
             int32 V_1,
             int32 V_2)
    IL_0000:  ldc.i4.5
    IL_0001:  newarr     [mscorlib]System.Int32
    IL_0006:  stloc.0
    IL_0007:  ldc.i4.0
    IL_0008:  stloc.1
    IL_0009:  br.s       IL_0015

    IL_000b:  ldloc.0
    IL_000c:  ldloc.1
    IL_000d:  ldloc.1
    IL_000e:  ldloc.1
    IL_000f:  mul
    IL_0010:  stelem.i4
    IL_0011:  ldloc.1
    IL_0012:  ldc.i4.1
    IL_0013:  add
    IL_0014:  stloc.1
    IL_0015:  ldloc.1
    IL_0016:  ldloc.0
    IL_0017:  ldlen
    IL_0018:  conv.i4
    IL_0019:  blt.s      IL_000b

    IL_001b:  ldc.i4.0
    IL_001c:  stloc.2
    IL_001d:  br.s       IL_003b

    IL_001f:  ldstr      "arr[{0}]={1}"
    IL_0024:  ldloc.2
    IL_0025:  box        [mscorlib]System.Int32
    IL_002a:  ldloc.0
    IL_002b:  ldloc.2
    IL_002c:  ldelem.i4
    IL_002d:  box        [mscorlib]System.Int32
    IL_0032:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0037:  ldloc.2
    IL_0038:  ldc.i4.1
    IL_0039:  add
    IL_003a:  stloc.2
    IL_003b:  ldloc.2
    IL_003c:  ldloc.0
    IL_003d:  ldlen
    IL_003e:  conv.i4
    IL_003f:  blt.s      IL_001f

    IL_0041:  call       int32 [mscorlib]System.Console::Read()
    IL_0046:  pop
    IL_0047:  ret
  } // end of method Test::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代碼大小       7 (0x7)
    .maxstack  1
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Test::.ctor

} // end of class Test

// =============================================================
//*********** 反彙編完成 ***********************
// WARNING: Created Win32 resource file Class2.res

  

咱們粗略的感覺是它很相似於早先的彙編語言,但它具備了對象定義和操做的功能。咱們能夠看到它定義並實現了一個繼承自System.Object 的HelloWorld類及兩個函數:Main()和.ctor()。其中.ctor()是HelloWorld類的構造函數,可在"HelloWorld.cs"源代碼中咱們並無定義構造函數呀--是的,咱們沒有定義構造函數,但C#的編譯器爲咱們添加了它。你還能夠看到C#編譯器也強制HelloWorld類繼承System.Object類,雖然這個咱們也沒有指定。

一塊兒來看看典型的C#/.NET應用程序的執行過程:
那麼PE文件是怎麼執行的呢?下面是一個典型的C#/.NET應用程序的執行過程:

(1)用戶執行編譯器輸出的應用程序(PE文件),操做系統載入PE文件,以及其餘的DLL(.NET動態鏈接庫)。 
(2)操做系統裝載器根據前面PE文件中的可執行文件頭跳轉到程序的入口點。顯然,操做系統並不能執行中間語言,該入口點也被設計爲跳轉到mscoree.dll(.NET平臺的核心支持DLL)的_ CorExeMain()函數入口。 
(3)CorExeMain()函數開始執行PE文件中的中間語言代碼。這裏的執行的意思是通用語言運行時按照調用的對象方法爲單位,用即時編譯器JIT將中間語言編譯成本地機二進制代碼,執行並根據須要存於機器緩存。 
程序的執行過程當中,垃圾收集器負責內存的分配,釋放等管理功能。 
程序執行完畢,操做系統卸載應用程序。
 
總之,在運行 Microsoft 中間語言 (MSIL) 以前,也就是咱們使用VS編譯後生成的exe文件,必須先根據公共語言運行庫將其編譯爲適合目標計算機體系結構的本機代碼。
 
C#將IL轉爲機器碼的兩種方法及區別 :
.NET Framework 提供了兩種方式來執行此類轉換:.NET Framework 實時 (JIT) 編譯器 和 .NET Framework 本機映像生成器 (Ngen.exe)。
 
JIT方式:因爲編譯器將本地代碼保存在動態內存中,因此關閉程序時本地代碼將發生丟失。當再次啓動程序或者同時運行程序的兩個實例時,JIT編譯器將再次將IL代碼編譯爲本地指令。
Ngen方式:公共語言運行庫支持一種提早編譯模式。此提早編譯模式使用本機映像生成器 (Ngen.exe) 將 MSIL 程序集轉換爲本機代碼,其做用與 JIT 編譯器極爲類似。
Ngen.exe 的操做與 JIT 編譯器的操做有三點不一樣:
(1)它在應用程序運行以前而不是運行過程當中執行從 MSIL 到本機代碼的轉換。
(2)它一次編譯一個整個的程序集,而不是一次編譯一個方法。
(3)它將本機映像緩存中生成的代碼以文件的形式持久保存在磁盤上。
 
引用:
相關文章
相關標籤/搜索