C#基礎拾遺系列之一:先看懂IL代碼


一、前言

首先,想說說為什么要寫這樣系列的文章,有時候在和同事朋友聊天的時候,經常會聽到這樣的話題:

(1)在這家公司沒什么長進,代碼太爛,學不到東西。(你有沒有想想框架為什么這樣寫,代碼還可以怎么去優化,比如公司使用Dapper,源碼研究過沒以及這樣封裝原因是啥)

(2)現在只會Ctrl + C  Ctrl +V  ,不排除有時為了效率,包括我自己有時候也懶的寫直接復制粘貼  (是不是感覺距離語言的本質越來越遠了)

(3)Ctrl + C  Ctrl +V 時間長了,都有點懷疑自己是否有勇氣面試其他公司 (是不是總給自己找借口,年齡大了,不敢瘋狂了,當然大家不要誤解,我沒鼓勵大家跳槽)

(4)干了幾年沒什么提高 (無論要精通那門技術,我們都應該從其本質出發)

最近也在反思自己,之前看到博客園大神:fish-li 的一篇文章《Fish Li 該如何幫助您呢?》其中說到:如何做一個有追求的技術人員,受益匪淺。以及張善友老師分享的關於雷果果的技術之路,大家都羡慕這些大神,何曾想過他們背后的付出,不要再抱怨環境不好,環境好也是給這些有准備和有追求的人,很感謝有這樣的前輩,現在的社會確實很浮躁,但與我何干,好了毒雞湯就灌到這里,大家如果有共鳴的話,不要表達出來了,默默想想。

二、IL指令

(1)什么是IL

IL(Intermediate Language),它也稱為CIL或者MSIL,中文就是“中間語言”。IL由ECMA組織(ECMA-335標准)提供完整的定義和規范。我們可以直接把C#源碼編譯為.exe或dll文件,但是此時編譯出來的程序代碼並不是CPU能直接執行的二進制代碼,而是IL代碼。

(2)反編譯工具

在這里使用ILDasm.exe,不適用其他的反編譯工具,這個工具在安裝完畢VS2010后就已經存在了,大家可以在開始菜單中輸入“IL”關鍵字進行搜索,如果沒有的話,到C:\Program Files(x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX4.0Tools

(3)IL 指令表

IL指令表

三、使用工具查看IL代碼

案例一:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string helloString = "Hello World";
            Console.WriteLine(helloString);
        }
    }
}

從上圖可以看到IL結構:包含MANIFEST文件和ConsoleApplication1,其中MANIFEST是一個清單文件,主要包括程序集的一些屬性,例如程序集名稱、版本號、哈希算法、程序集模塊,以及對外部引用程序的引用項目。.Program類是我們要主要介紹的內容。

(1)MANIFEST清單文件介紹

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly ConsoleApplication1
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.

  // --- 下列自定義屬性會自動添加,不要取消注釋 -------
  //  .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) 

  .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 13 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63   // ...ConsoleApplic
                                                                                              61 74 69 6F 6E 31 00 00 )                         // ation1..
  .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 13 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63   // ...ConsoleApplic
                                                                                                61 74 69 6F 6E 31 00 00 )                         // ation1..
  .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20   // ...Copyright .. 
                                                                                                  20 32 30 31 38 00 00 )                            //  2018..
  .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 62 35 32 39 36 38 63 32 2D 64 66 63 33   // ..$b52968c2-dfc3
                                                                                                  2D 34 65 38 31 2D 38 32 64 32 2D 64 39 66 35 62   // -4e81-82d2-d9f5b
                                                                                                  62 33 32 38 33 64 37 00 00 )                      // b3283d7..
  .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 )             // ...1.0.0.0..
  .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1C 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B   // ....NETFramework
                                                                                                        2C 56 65 72 73 69 6F 6E 3D 76 34 2E 35 2E 32 01   // ,Version=v4.5.2.
                                                                                                        00 54 0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73   // .T..FrameworkDis
                                                                                                        70 6C 61 79 4E 61 6D 65 14 2E 4E 45 54 20 46 72   // playName..NET Fr
                                                                                                        61 6D 65 77 6F 72 6B 20 34 2E 35 2E 32 )          // amework 4.5.2
  .hash algorithm 0x00008004
  .ver 1:0:0:0
}
.module ConsoleApplication1.exe
// MVID: {A09A2101-9A49-483A-A224-5D2E14D231A6}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00020003    //  ILONLY 32BITREQUIRED
// Image base: 0x01110000
View Code

介紹:

指定當前程序集需要引用的外部程序集,如上面 ConsoleApplication1.exe就是引用了mscorlib.

.publickeytoken = (標記 ) 指定所引用程序集的實際公鑰標記。公鑰能唯一確定程序集。

.ver指定引用程序集的版本

.assembly 程序集名稱  指定程序集名稱

.hash algorithm int32值  :  指定使用的hash算法

.ver :指定程序集版本號

.module ConsoleApplication1.exe    指定組成程序集的模塊名稱,在此示例中,程序集中只包含一個文件
.subsystem 0x0003 // WINDOWS_CUI  指定程序要求的應用程序環境。在此示例中,0x0003表示該可執行文件從控制台運行
.corflags 0x00020003 // ILONLY 32BITREQUIRED 當前是元數據中的一個保留字段

.class表示的Program是一個類,extends 代表Program類繼承於程序集mscorlib中的System.Object類,這就告訴我們,在C#中所有的類的父類都是Object。

private為訪問權限,表明該類是私有的。

auto:表明程序加載的時候內存布局是有CLR決定的,而不是由程序本身控制的。

ansi:表明類的編碼為ansi編碼

beforefieldinit :表明CLR可以在第一次訪問靜態字段之前的任何時刻執行類型構造函數。類型構造函數也就是構造函數,而使用beforefieldinit屬性可以提高性能。

.ctor 表示構造函數

cil managed 表明方法體中的代碼是IL代碼,且是托管代碼,即運行在CLR運行庫中的代碼

.maxstack 表明執行構造函數時,評估堆棧可容納數據項的最大個數。評估堆棧是保存方法中所需變量的值的一個內存區域,該區域在方法執行結束時會被清空,或者存儲一個返回值

IL_0000是代碼行的開頭一般在IL_標記之前的部分為變量的聲明和初始化操作

ldarg.0表明加載第一個成員參數,其中ldarg是load argument 的縮

call 指令一般用於調用靜態方法,而這段代碼中call指令並不是在調用靜態函數,而是調用System.Object構造函數。另外一個指令則一般用來調用實例方法,它的調用過程是:首先檢查被調用的函數是否為虛函數,

如果不是就直接調用,如果是則檢查子類是否重寫,如果有重寫就調用子類中的實現,如果沒有重寫就繼續調用原來函數。

ret 指令表示執行完畢,就是return的縮寫

 

最后是Main函數,它是應用程序的入口函數:

hidebysig指令表示如果當前類作為父類,用該指令標記的方法將不會被子類繼承

.entrypoint指令代表該函數是程序的入口函數,每個托管應用程序都有且只有一個入口函數,CLR加載的時候,首先從.entrypoint函數開始執行。

.locals init ([0] string helloString) 表示定義string 類型的變量,變量名成為:helloString

IL_0000: nop 表示不做任何操作 No Operation

ldstr "Hello World" :  ldstr:推送對元數據中存儲的字符串的新對象引用   表示:將字符串“Hello World” 壓入評估棧,此時“Hello World” 處於評估棧的棧定,棧是一種數據結構,具有先進后出的特性。

stloc.0  :從計算堆棧的頂部彈出當前值並將其存儲到索引 0 處的局部變量列表中(也就helloString)  在此示例中:就是把字符串"Hello World" 賦值給變量helloString

ldloc.0 :將索引 0 處的局部變量加載到計算堆棧上。也就是:把變量helloString 加載到計算堆棧上

(以ld為前綴的指令表示:入棧操作  st為前綴的指令則代表着出棧操作)

call :指令表示調用靜態函數, 這里調用的是Console類中的WriteLine函數,把第0個局部變量輸出到控制台中

案例二:

(1)IL基本類型

任何都有其內置的類型,IL語言也不例外,因為C#語言最終都會編譯成IL代碼,所以兩者必然存在一種對應關系:

 (2)IL變量的聲明

.locals 指令代表變量的聲明,聲明語句放在IL_標記前面。例如在前面的程序中,

.locals init ([0] string helloString)  就聲明了一個名為helloString的變量,其中類型為string

(3)基本運算符

算數運算符:加法指令add   、乘法指令sub、出發指令div、以及求余指令rem等

位運算符:包括一元指令not 、與指令and、或指令or,結果以1 和 0 分別表示真、假,運算結果壓入評估棧棧頂

比較運算:包括大於指令cgt、小於指令clt 和等於指令 ceq

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ILAdd
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 2;
            int j = 3;

            int result = i + j;

            Console.WriteLine(result);

        }
    }
}

 

 IL代碼分析如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代碼大小       17 (0x11)
  .maxstack  2
//聲明三個變量
  .locals init ([0] int32 i,
           [1] int32 j,
           [2] int32 result)
//不做任何操作
  IL_0000:  nop
//將整數值 2以4字節 作為 int32 推送到計算堆棧上
  IL_0001:  ldc.i4.2
//把評估棧頂的值彈出,並賦值給第0個局部變量(即 i),等價於i = 2
  IL_0002:  stloc.0
//將整數值 3以4字節 作為 int32 推送到計算堆棧上
  IL_0003:  ldc.i4.3
//把評估棧頂的值彈出,並賦值給第1個局部變量(即 j),等價於j = 2
  IL_0004:  stloc.1
//把第0個變量壓入評估棧,即把變量 i 壓入評估棧
  IL_0005:  ldloc.0
//把第1個變量壓入評估棧,即把變量 j 壓入評估棧
  IL_0006:  ldloc.1
//執行add操作,之后將把變量i和j清空,並把操作結果保存在評估棧站頂
  IL_0007:  add
//把站頂的值彈出,並賦值給第二個局部變量(即result) ,此時result即為i+j 的值,因為棧頂為兩個值的和
  IL_0008:  stloc.2
//將索引 2 處的局部變量加載到計算堆棧上。就是result
  IL_0009:  ldloc.2
//call調用靜態函數
  IL_000a:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000f:  nop
//返回
  IL_0010:  ret
} // end of method Program::Main
View Code
static void Main(string[] args)
        {
            int i = 2;
            if (i>0)
            {
                Console.WriteLine("i為正數");
            }
            else
            {
                Console.WriteLine("i為0或負數");
            }
        }

IL分析如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代碼大小       40 (0x28)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] bool V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.2
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  ldc.i4.0
//執行大於指令,比較i與0,運算結果存在於評估棧棧頂,1表示真,即i>0為真
  IL_0005:  cgt
//就是把比較后的值賦值個變量V_1
  IL_0007:  stloc.1
// 把變量V_1壓入評估棧
  IL_0008:  ldloc.1
//如果 value 為 false、空引用或零,則將控制轉移到目標指令 
  IL_0009:  brfalse.s  IL_001a
  IL_000b:  nop
//推送對元數據中存儲的字符串的新對象引用。
  IL_000c:  ldstr      bytearray (69 00 3A 4E 63 6B 70 65 )                         // i.:Nckpe
  IL_0011:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0016:  nop
  IL_0017:  nop
//無條件地將控制轉移到目標指令(短格式)
  IL_0018:  br.s       IL_0027
  IL_001a:  nop
//推送對元數據中存儲的字符串的新對象引用。
  IL_001b:  ldstr      bytearray (69 00 3A 4E 30 00 16 62 1F 8D 70 65 )             // i.:N0..b..pe
  IL_0020:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0025:  nop
  IL_0026:  nop
  IL_0027:  ret
} // end of method Program::Main
View Code

案例三:const的本質

namespace ConstIL
{
    class Program
    {
        static void Main(string[] args)
        {
       
            Console.WriteLine(Person.num);
        }
    }

    public class Person
    {
        /// <summary>
        /// 這個就是一個所謂的const常量
        /// </summary>
        public const int num = 10;
    }
}
View Code

 IL代碼分析:

為什么可以直接類名.num?這種語法只有在該常量為static修飾是才可以,下面我們來看看IL:

.field public static literal int32 num = int32(0x0000000A)

 看到沒,const其實就是一個static的變量,一個靜態的值,因為它是跟着類走的。而不是實例。所以 const的特征如下:

(1)固定不變的值。

(2)在編譯的時候就已經確定了。

(3)在初始化的時候設置值

 

好了,先寫到這里,回家前寫這一篇,希望對你有幫助。

 

 參考書籍:《Learning Hard》

參考文章:http://www.cnblogs.com/flyingbirds123/archive/2011/01/29/1947626.html

 

作者:郭崢

出處:http://www.cnblogs.com/runningsmallguo/

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。


免責聲明!

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



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