zoukankan      html  css  js  c++  java
  • C#基础拾遗系列之一:先看懂IL代码

    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 SDKsWindowsv7.0ABinNETFX4.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 )                         // .zV.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)在初始化的时候设置值

    好了,先写到这里,回家前写这一篇,希望对你有帮助。

  • 相关阅读:
    初试django
    初试mysql
    ASP.NET MVC 做的网站项目
    Update 更新语句使用两个表关联
    SQL Server 输出所有表结构
    循环数据集字段赋默认值
    FireBird.conf配置文件常用参数
    Delphi 获取临时数据集 ClientDataSet
    DELPHI 读取csv 格式文本文件
    获取 临时数据集 的两种方法
  • 原文地址:https://www.cnblogs.com/burningmyself/p/9012319.html
Copyright © 2011-2022 走看看