IL代码简单分析(1)

对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道你的代码是如何运行相互调用的,此言一出不明觉厉

  然后开始接触IL,了解了一段时后才发现原来读懂IL代码并不难。进入正题

   1.1  什么是IL

  IL是.NET框架中中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码(来源百度)

   1.2 为什么要了解IL

    在很多时候不明白代码是如何操作时就可以通过IL指令来解释,比如,装箱,拆箱是否只是听别人说或者书上讲是怎么怎么实现的,自己是否证实过呢?了解IL指令你可清楚看到是每一步是如何处理的

   1.3  怎么学IL

   世上有个定律叫“二八定律” ,80%的功能,只要用20%的技术就可以完成,但要完成另外20%可能就需要80%技术了,对于IL代码也是如此,有200多个指令,我们只需要用到其20%的指令就可以解决我们80%的问题了,所以我不会写太多,只是让大家能看懂普通的程序代码编译成IL代码后就行了,还有就是要多看,IL代码的每一条指令都是特定的意思,看得多了自然就懂了,当对自己代码有疑问时尝试看看它对应的IL代码,也许你会了解得更多。

 IL指令大全  点这里

 IL代码编译器 ILDasm   点这里

二 如何查看IL代码

  2.1 步骤

   1 编写代码并编译通过

   2  找到源文件的obj文件下的 .exe文件

     3 导入到ILDasm中反编译成IL代码

上图

1 -2步                                                     3  导入到ILDasm中                      

 

     


 ctor代表是构造函数,


  ILDasm中图标含义

 

 

 

三  如何读IL(大致了解)

  以上步骤完成后我们就可以看到代码被编译后的IL代码,以下部份将会对每一条IL指令做详细的解释

C#代码 

复制代码
1         static void Main(string[] args)
2         {
3             int i = 1;
4             int j = 2;
5             int k = 3;
6             Console.WriteLine(i+j+k);
7         }
复制代码

 

IL代码

 // Call  Stack 是一个局部变量列表,用于存储 .locals init (int32 V_0,int32 V_1,int32 V_2)初始化后的参数 V_0,V_1,V_2

// Evaluation Stack 是一个栈 ldc.i4.2 这种指令都会先把值压入栈中等待操作

在第四段时大家可以理解得更清楚一点

复制代码
 1  .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint  //程序入口
 4   // Code size       19 (0x13)
 5   .maxstack  3  //定义函数代码所用堆栈的最大深度,也可理解为Call Stack的变量个数
6 //以下我们把它看做是完成代码中的初始化 7 .locals init (int32 V_0,int32 V_1,int32 V_2) //定义 int 类型参数 V_0,V_1,V_2 (此时已经把V_0,V_1,V_2存入了Call Stack中) 8 IL_0000: nop //即No Operation 没有任何操作,我们也不用管它
9 IL_0001: ldc.i4.1 //常量1压栈     (压入Evaluation Stack中) 10 IL_0002: stloc.0 //把"i"赋值给Call Stack中第0个位置(V_0)   
11   IL_0003:  ldc.i4.2    //常量2压栈     (压入Evaluation Stack中) 
12   IL_0004:  stloc.1     //把"j"赋值给Call Stack中第1个位置(V_1)
13   IL_0005:  ldc.i4.3    //常量3压栈       (压入Evaluation Stack中)
14   IL_0006:  stloc.2     //把 "k" 赋值给Call Stack中第2个位置(V_2)
15 
16    //上面代码初始化完成后要开始输出了,所要把数据从Call Stack中取出
17 
18   IL_0007:  ldloc.0     //取Call Stack中位置为0的元素(V_0)的值("i"的值)  (相当于Copy一份值Call Stack中V_0的值。V_0本身的值是不变的)
19   IL_0008:  ldloc.1     //取Call Stack中位置为1的元素(V_1)的值("j"的值)     (同上)
20   IL_0009:  add         // 做加法操作,栈中两哥
21   IL_000a:  ldloc.2     // 取出Call Stack中位置为2的元素(V_2)的值("k"的值)
22   IL_000b:  add         // 做加法操作
23   IL_000c:  call       void [mscorlib]System.Console::WriteLine(int32) //调用输出方法
24   IL_0011:  nop
25   IL_0012:  ret         //即为  return  标记 返回值
26 } // end of method Program::Main
复制代码

 指令详解

.maxstack:代码中变量需要在Call Stack 中占用几个位置

.locals init (int32 V_0,int32  V_1,int32 V_2):定义变量并存入Call Stack中

nop:即No Operation 没有任何操作,我们也不用管它,

ldstr.:即Load String 把字符串加压入Evaluation Stack中 

stloc.:把Evaluation Stack中的值弹出赋值到Call Stack中

ldloc.:把Call Stack中指定位置的值取出(copy)存入 Evaluation Stack中   以上两条指令为相互的操作stloc赋值,ldloc取值

call:  调用指定的方法

ret: 即return  标记返回

  每一句IL代码都加了注释后,是不是觉得IL代码其实并不难呢,因为它的每一条指令都是固定的,你只要记住了,看IL代码就比较轻松了。

 

四 如何读IL(深入了解)

4.1 提出问题

  有了上面的一点IL基础后,现在我们来深入一点点,

  有如下几个问题:

  1  当 ldc.i4.1 这一指定加载 “i” 这个变量后并没有马上赋值给Call Stack中的元素,而是要执行 stloc.0 后才赋值,那没赋值前是存在哪里的呢?

  2 ldloc.0  把元素取出来后,存在哪里的?

  3 add操作完成后值存在哪里?

4.2 概念引入

  Managed Heap:這是動態配置(Dynamic Allocation)的記憶體,由 Garbage Collector(GC)在執行時自動管理,整個 Process 共用一個

  Managed Heap(我理解为托管堆,存储引用类型的值)。

  Evaluation Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Evaluation Stack(我理解为类似一个临时存放值类型数据的线程栈)

  Call Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Call Stack。每呼叫一次 method,就會使得 Call Stack 上多了一個 Record     Frame;呼叫完畢之後,此 Record Frame 會被丟棄(我理解为一个局部变量表,用于存放.locals init(int32 V_0)指令的参数值如:V_0)

 

4.3  IL指令详解

 对三个名词做解释后现在我们再来仔细看看执行IL指令时,对应的变量是如何存放的

IL_0001:  ldc.i4.1    //加载第一个变量i  
首先对 ldc.i4.1 做下细解:变量的值为1 时IL指令就是ldc.i4.1 ,变量值为2 时IL指令就是ldc.i4.2,依此类推一直到ldc.i4.8
当为-1 时IL指令为ldc.i4.M1,当超过8时就是一个统一指令 ldc.i4.S

IL_0001: ldc.i4.1 //加载第一个变量i

当执行这一条指令时会把变量i 的值压入Evaluation Stack中做临时存储



IL_0002:  stloc.0     //把i 赋值给Call Stack中第0个位置
当执行这一条指信时会把Evaluation Stack 中的 i 弹出赋值给Call Stack中的第0个位置



IL_0007:  ldloc.0     //取出Call Stack位置为0的元素 (i)
当执行这条指令时会将 Call Stack中的位置为0的元素的值取出(copy)压入Evaluation Stack 等待做加法的指令 Add

  IL_000b:  add         // 做加法操作
  add这一操作完成后,会把结果存在Evaluation Stack中等待下一步的指令操作
4.4 问题回答
  以上内容看完开始的问题相应也解决了
  1 ldc.i4.1 把值取出来后先存在 Evaluation Stack中 执行了stloc.0 后才会存入Call Stack中指定的元素中
  2 ldloc.0 把取出来后也是先压入 Evaluation Stack 等持指令
  3 add 操作完成后值是暂存于 Evaluation Stack中的


以上把IL指令是如何操作内存中的值做了一点很基本的介绍,让大家在了解IL指令时,知道是如何操作内存中的值的。我想对于理解IL指令或许更透彻一点。



五 总结



  这一篇只写了IL中最基本的几个指令,然后讲解了IL指令是如何操作内存中数据的。古人云:水得一口一口喝,路得一步一步走,步子迈得大了容易扯着蛋,慢慢来内容虽然少了点,但是还会有下篇的。下一篇还是会写IL的一些基本指令,我会结合我自己的理解,尽量把文字写得通俗一点,让大家更容易理解。


 

表1-3 IL常用指令

指令

描述

.assembly <程序集名称> {}

设置程序集

ldc.i4.n

把一个 32位的常量(n从0到8)装入堆栈

stloc.n

把一个从堆栈中返回的值存入第n(n取0~8)个局部变量

add

2个值相加。命令的参数必须在调用前装入堆栈,该函数从堆栈中移除参数并把运算后的结果压入堆栈

sub

2个值相减

mul

2个值相乘

newarr type

生成一个元素类型为type 的数组。数组的大小必须在调用该命令前装入堆栈。该命令会把一个数组的引用装入堆栈

stelem.i4

给一个数组成员赋值。数组的引用、下标和值必须在调用该命令前装入堆栈

ldelema type

把数组元素的地址装入堆栈。数组的引用和下标必须在调用该命令前装入堆栈。地址用来调用非静态函数

ldlen

把数组的长度装入堆栈。数组的引用必须在调用该命令前装入堆栈

ldloca.svariable

把变量的地址装入堆栈

ldc.i4.s value

把一个Int32的常量装入堆栈(用于大于8位的数)

conv.i4

把堆栈中值转换成Int32类型

call instancefunction(arguments)

调用类的非静态函数

bge.s label

跳转至label 如果value1≥value 2. Values 1和 2 必须在调用本命令前装入堆栈

br.s label

跳转至label

box value type

把一个值类型转成一个Object,并把该Object的引用装入堆栈

blt.s label

跳转至label 。如果value 1小于 value 2. Values 1 和 2 必须在调用本命令之前装入堆栈

ldelem.i4

把一个数组元素装入堆栈。数组引用和下标必须在调用本命令之前装入堆栈

ldarga.sargument

把函数参数的地址装入堆栈

dup

在堆栈上复制一个值

stind.i4

存储值的地址。地址和值必须在调用本命令之前装入堆栈

.field

定义类成员。和关键字public、private、static等一起使用

stsfld static field

用堆栈中的值替换静态字段的值

ldfld field

把一个非静态字段装入堆栈。类实例的地址必须在调用本命令之前装入堆栈

ldarg.n

把第n个参数装入堆栈。在非静态函数中,第0个参数是一个隐含的参数,代表this

newobjconstructor

用构造函数constructor生成一个类的实例。构造函数的参数必须在调用本函数之前先装入堆栈。一个类的实例会被生成并装入堆栈

callvirt instancefunction

调用一个对象的后期绑定方法

 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值