zoukankan      html  css  js  c++  java
  • 关注底层:IL部分

    园子里两个大牛正争的如火如荼,小生不才,借一下两个名人的名气也来谈一下Microsoft intermediate language MSIL,就是大家口里的IL)和ASM(这里指针对X86汇编,排除其他一切“高级汇编”)。

    为了达成共识,我们先对一些概念回忆一下:

    CPU只能执行机器码,不能执行IL

    这个应该没有什么疑问吧。机器码就是传说的01的组合,虽然今天的CPU运算速度已经非常非常快,而且非常非常智能,但它和上个世纪的CPU还是一样,还只能认识01的组合的这种机器码。

    说到IL就应该提一提编译器的前端和后端。众所周知,微软的.NET平台上有众多的语言,大家熟悉的有C#VB.NETJscript.NET,而这些不同语法的“高级”语言,经过CSC.ExeC#编译器)、VBC.ExeVB.NET编译器)等编译后得到IL,这之前的部分我们称之为前端,实际上事情并没有到这里结束,当CLR加载托管程序集,运行某一个方法时,CLR发现这个方法还没有被即时编译(JIT),这个时候就会调用JIT编译器对这个方法的IL编译,编译的结果就是我们的目标代码(目标代码可以是汇编代码或机器码,这里不加以区分)。而这之后的JIT编译等过程我们可以认为是编译器的后端。

    有了上面这段描述,各位同学大脑中应该有这样一幅画面:

     

    通过这幅图我们看到,微软通过实现不同的前端,而共一个后端实现了一个平台,多种语言的目标。这也就是为什么你用VB.NET写的组件,我用C#可以直接使用,甚至是
    我用
    C#写的类直接派生自一个VB.NET写的类。

    因为IL相对于机器码来说相对简单,因为IL不能操作寄存器。所以你甚至可以自己定义一个语法,然后实现一个编译器的“前端”,将你自己的语言加入到.NET这个大家族
    中(貌似园子里的装配脑袋正在做这方面的工作)。这样你自己的语言也可以享受
    .NET的类库了,.NET的垃圾回收机制了。

    从这里我们了解到IL起一个桥梁的作用。那好,我们学习IL到底可以干些什么?

    1:探究C#这些编译器内部所作所为:

    记得以前博客园有一场对访问集合对象时,使用for好还是foreach好的大讨论,那我就用这个例子来用IL说明一下使用for访问

     static void Main(string[] args)
            {
                ArrayList arr 
    = new ArrayList();
                
    for (int i = 0; i < arr.Count; i++)
                { 
                    
    object o = arr[i];
                }
    }

    对应IL代码如下:

    .method private hidebysig static void  Main(string[] args) cil managed
    {
    //标明这是本程序的入口点
      .entrypoint
      
    // Code size       32 (0x20)
      .maxstack  2
     
    //声明两个局部变量,一个ArrayList类型的arr,一个用在for循环中的整型i
      .locals init ([0class [mscorlib]System.Collections.ArrayList arr,
               [
    1int32 i)
    //调用ArrayList的构造函数,实例化对象,并将对象的引用放到IL的运算栈上 
     IL_0000:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
     
    //将IL运算栈上顶部的一项弹出,赋值给本方法的第一个局部变量
      IL_0005:  stloc.0
    //将一个四字节的整型0放到IL的运算栈上
      IL_0006:  ldc.i4.0
    //将IL运算栈顶部的一项弹出,复制给本方法的第二个局部变量
      IL_0007:  stloc.1
    //跳转到IL_0016处
      IL_0008:  br.s       IL_0016
    //将本方法的第一个局部变量加载到运算栈顶部
      IL_000a:  ldloc.0
    //将本方法第二个局部变量加载到运算栈顶部
      IL_000b:  ldloc.1
    //弹出运算栈最顶部项,在这里就是arr,调用arr的get_Item(int32)方法,并且将结果放//到运算栈顶部
      IL_000c:  callvirt   instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)
    //弹出运算栈顶部,在这里注意到,C#的代码将arr[i]赋值给了object对象,但为什么没有//对应IL代码呢,原来C#编译器发现这个object对象o并没有什么用,所以就直接丢弃了,//呵呵,还挺智能的
      IL_0011:  pop
    //加载本方法第二个局部变量到运算栈顶部
      IL_0012:  ldloc.1
    //加载四字节整型1到运算栈顶部
      IL_0013:  ldc.i4.1
    //弹出运算栈顶部两项,相加,将相加后的结果放到运算栈顶部
      IL_0014:  add
    //将运算栈顶部一项赋值给本方法的第二个局部变量(聪明的你从这里应该可以猜测得到,//这里就是for循环中的i++了)
      IL_0015:  stloc.1
    //将本方法的第二个局部变量加载到IL运算栈
      IL_0016:  ldloc.1
    //将本方法的第一个局部变量加载到IL运算栈
      IL_0017:  ldloc.0
    //弹出运算栈顶部一项,调用该项的get_Count()方法,从上面代码可以看出运算栈顶部一//项就是本方法的第一个局部变量,也就是arr,方法调用后的结果会保存到运算栈顶部(从//这里也可以看出读取C#里的Count属性原来就是调用get_Count()方法,这也是IL的一个作//用,你现在应该明白了为什么大家都说.NET中的属性其实是两个方法吧)
      IL_0018:  callvirt   instance int32 [mscorlib]System.Collections.ArrayList::get_Count()
    //弹出运算栈顶部的两项,比较大小,在这里就是arr.Count和i,如果i小于arr.Count,则//跳转到IL_000a
      IL_001d:  blt.s      IL_000a
    //不用多说,退出方法
      IL_001f:  ret
    }

    上面就是Main这个方法对应的IL代码,从注释中可以看出,IL是基于一个运算栈的,所有的操作数就是从这运算栈里倒来倒去,运算栈也是一个虚拟的概念,
    不要想着把它与物理计算机里的内存或寄存器相对应起来,因为
    IL本来就是运行在CLR这个虚拟机之上的。看了使用for访问的代码,我们来看看使用foreach访问的代码
    (与上面相同的部分我就不注释了):

    .method private hidebysig static void  Main(string[] args) cil managed
    {
      
    .entrypoint
      
    // Code size       50 (0x32)
      .maxstack  1
    //声明四个局部变量啊
      .locals init ([0class [mscorlib]System.Collections.ArrayList arr,
               [
    1object item,
               [
    2class [mscorlib]System.Collections.IEnumerator CS$5$0000,
               [
    3class [mscorlib]System.IDisposable CS$0$0001)
      
    IL_0000:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
      
    IL_0005:  stloc.0
      
    IL_0006:  ldloc.0
    //注意,调用ArrayList的GetEnumerator()方法,获取ArrayList的迭代器
      IL_0007:  callvirt   instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
      
    IL_000c:  stloc.2
    //呵呵,还加了一个try,为的是能在finally的时候调用Dispose()方法
      .try
      {
        
    IL_000d:  br.s       IL_0016
    IL_000f:  ldloc.2
    //不再是使用ArrayList的get_Item(int32)方法了,是使用迭代器的get_Current()
        IL_0010:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        
    IL_0015:  stloc.1
    IL_0016:  ldloc.2
    //调用迭代器的MoveNext方法
        IL_0017:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        
    IL_001c:  brtrue.s   IL_000f
        
    IL_001e:  leave.s    IL_0031
      }  
    // end .try
      finally
      {
        
    IL_0020:  ldloc.2
        
    IL_0021:  isinst     [mscorlib]System.IDisposable
        
    IL_0026:  stloc.3
        
    IL_0027:  ldloc.3
        
    IL_0028:  brfalse.s  IL_0030
        
    IL_002a:  ldloc.3
        
    IL_002b:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        
    IL_0030:  endfinally
      }  
    // end handler
      IL_0031:  ret
    }

    哦,这样一比较,for和foreach的不同点就自然而然的明白了。对于这些“语法糖”,我们只要打开IL一切都明明白白,C# 3.0的语法糖为数众多,为什么很多
    人知道他到底是怎样实现的呢,还有很多人叫嚷着,这没什么,语法糖而已,因为大家都明白IL没变啥,只是编译器使坏。

    2.加密解密

    加密解密在Win32时代就有之,不过自从.NET的出现这块就更容易了,很多加密的方法,只需要打开IL,然后改一点东西,再用微软提供的ILAsmer汇编回去,这样就破解了。
    这块内容很复杂,园子里也有很多文章。

    那学IL有没有用呢?当然有用。学习IL你可以看到表面上看不到的东西,如果你是一位痴迷于技术的同学,那IL就应该好好学学了,把CLR看做“计算机”,那IL就是CLR的“本地代码”
    (实际上不正确,
    CLR自己并不直接运行ILCLR还是需要将IL编译为真正的本地代码然后执行)。

    .NET的水很深,实际上针对编译器前端这部分水不是很深。内核或原理部分都是在CLR这部分,比如程序集如何加载?方法如何编译?多态的实现方法?那要探究这部分的原理怎么办?这个
    时候从
    IL就无法知晓了,当然你可以google之或百度之,你也可以买很多大牛的书籍看看。不过你想不想自己弄明白呢?毕竟如果不自己加以试验得出结论,那些东西我觉得还是书本上的,
    我们获得的知识也是靠记忆获得的,如果你能使用一个调试工具,亲自打开
    JIT之后的结果,你就知道多态到底是咋回事?

    要知后事,请听下回分解!

     本来想写三篇的:

    关注底层(上):IL部分

    关注底层(中):汇编 for .NETer

    关注底层(下):Why and How

    不过发现我想讨论的内容老赵已经说了,而且说得更好,所以觉得后面两篇也没有必要出了。所以就留此一篇作为纪念吧。 

  • 相关阅读:
    HDL之Bitwise operation
    Embedded之memory type
    RFID之UID
    VHDL之package
    QS之vsim
    潭州课堂25班:Ph201805201 爬虫高级 第八课 AP抓包 SCRAPY 的图片处理 (课堂笔记)
    潭州课堂25班:Ph201805201 爬虫高级 第七课 sclapy 框架 爬前程网 (课堂笔)
    潭州课堂25班:Ph201805201 爬虫高级 第六课 sclapy 框架 中间建 与selenium对接 (课堂笔记)
    潭州课堂25班:Ph201805201 爬虫高级 第五课 sclapy 框架 日志和 settings 配置 模拟登录(课堂笔记)
    潭州课堂25班:Ph201805201 爬虫高级 第四课 sclapy 框架 crawispider类 (课堂笔记)
  • 原文地址:https://www.cnblogs.com/yuyijq/p/1494324.html
Copyright © 2011-2022 走看看