IL代码底层运行机制

原创 2003年10月03日 14:22:00

                               IL代码底层运行机制

                                                刘强   

Cambest@sohu.com

200358

 

       大家都知道,和Java一样,C#也是基于堆栈的语言。也许对一般人来说,底层的运行细节并不是很重要;但了解这些,对我们理解、运用C#是很有帮助的。下面,我就通过一个很简单的例子来说明IL代码的底层运行机制,也许对你会有一些帮助。

 

       我给出的例子表面上看是一个实现整数相减功能的函数;实际上,我也不知道究竟能够干什么。在实际当中,我们的程序当中会有很多种数据类型、引用类型,为了简便起见,我所给出的示例代码只用了一种数据类型,如下所示:

public int Sub(int i,int j)

        {

            int s;

            int t = 0;

            int r = 4;

            s=i;

            r =i – j;

            r + =s + t;

            return r;

        }

这段代码很简单,任何学过C#的人都能看懂。首先,传入两个整型变量ij,然后经过内部运算,返回一个整型值。函数体内定义了三个局部变量st r,分别用于保存自定义值以及结果。我们可以将它包装进一个类中,然后将它编译成.dll装配件。运用VS.NET自带的ildasm反汇编工具进行反汇编,我们得到如下IL代码:

.method public hidebysig instance int32  Sub(int32 i,

                                       int32 j) cil managed

{

  // Code size       22 (0x16)

  .maxstack  3

  .locals init (int32 V_0, int32 V_1, int32 V_2, int32 V_3)

    ldc.i4.0

    stloc.1

    ldc.i4.4

    stloc.2

    ldarg.1

    stloc.0

    ldarg.1

    ldarg.2

    sub

    stloc.2

    ldloc.2

    ldloc.0

    ldloc.1

    add

    add

    stloc.2

    ldloc.2

    stloc.3

    br.s       IL_0014

    ldloc.3

    ret

}

IL代码也可以由VS.NET自带的IL编译工具ilasm编译为.dll装配件或.exe可执行文件。

这里,我要对IL中出现的符号作一下简单解释。以点号’.’开头的标号为伪指示代码,只起指示作用,最终不会被JIT编译为本地可执行代码,如“.method”,“.locals”等。而不带点号’.’的标号为IL汇编代码,它们在运行时将会被JIT编译为本地可执行代码,如“ldarg.1”等。

每条语句究竟代表了什么样的操作,我们下面在详细讲解。注意:局部变量的下标从0开始,因此要注意我下面所说的“第零个局部变量”等的含义。

首先,让我们看一看函数体内的第一条语句:.maxstack  3。从其本身我们也可以猜出该语句说明堆栈的大小。暂且不表,且看下文。

第二句:.locals init (int32 V_0, int32 V_1, int32 V_2, int32 V_3) V_0V_1V_2和我们在CS源程序中定义的局部变量str一一对应,我们大概也能猜到这一句是完成局部变量初始化工作的,但为什么在这里是四个呢?我们明明只定义了三个变量的。那么这由C#编译器自动维护的第四个变量有何作用?也暂且不表,先看下文。

ldc.i4.0

这条语句作用是在堆栈中载入常数,i4表示该常数为双字长的32位整型数,初始值为0。“ldc”可以理解为“load constant”,加载常数。如图a,它完成的操作如同(top)<=0top=top+1

          stloc.1

这条语句作用是将当前栈顶元素存入第一个局部变量。’1’表示操作对象为第一个局部变量。“stloc”可以理解为“store to local”,保存局部变量。如图b,它完成的操作如同top=top-1s<=(top)

          ldc.i4.4

这条语句完成的操作如同(top)<=4top=top+1,如图c

          stloc.2

这条语句完成的操作如同top=top-1t<=(top),如图d

          ldarg.1

          ldarg.2

这两条语句作用是在堆栈中载入第一个参数(i)、第二个参数(j)(和局部变量不同,参数的指示下标从1开始)。它完成的操作如同 (top)<=itop=top+1,(top<=jtop=top+1,如图e。其中,“ldarg”可以理解为“load argument”,加载参数。

          sub

这条语句作用是将当前栈顶元素求反,再下加到第二个栈单元中,如图f。它完成的操作如同top=top-1temp= -top),top=top-1,(top=top+ temptop=top+1

 

 

 

 

top

0

 

 

 

 

top

 

 

 

top

4

 

 

 

 

top

 

 

top

j

i

 

 

 

top

i-j

   

 

 

 

 

 

     (a)              (b)             (c)            (d)          (e)              (f)

 

          stloc.2

这条语句作用是将当前栈顶元素存入第二个局部变量(r)。它完成的操作如同top=top-1r<=(top),即r=i-j,如图

          ldloc.2

       ldloc.0

       ldloc.1

这三条语句作用是分别将第二、第零个、第一个局部变量加载到堆栈上,如图h。“ldloc”可以理解为“load local variable”,加载局部变量。

          add

          add

add的用发和sub一样,只不过不将当前栈顶元素求反,再下加到第二个栈单元中,连续操作两次。如图ij

          stloc.2

将当前栈顶元素存入第二个局部变量。如图k

          ldloc.2

在堆栈中载入第二个局部变量(r),如图l

 

 

 

 

 

top

 

 

 

top

r

 

 

 

 

top

 

 

 

top

r+s+t

 

 

top

s+t

r

 

top

t

s

r

    

 

 

 

 

 

 

      (g)              (h)             (i)              (j)               (k)           (l)

 

          stloc.3

将当前栈顶元素存入第三个局部变量,亦即保存返回值,如图m所示。

          br.s   IL_0014

跳转到下一句(ldloc.3)。如图n所示。

          ldloc.3

          ret

将第三个局部变量(也即由编译器自动维护的变量)加载到堆栈上,然后返回,如图o。从这里我们也可以看出,1、第三个变量和返回值类型相同;2、扫尾后、返回前将第三个局部变量加载至堆栈。这可以让我们确定:第三个变量用于存储返回值。我们还要弄清楚为什么要专门分配一个局部变量来存储返回值,这一点在后面会有说明。

 

 

 

 

 

top

 

 

 

 

top

 

 

 

top

V_3

      

 

 

 

 

 

 

       (m)          (n)            (o)      

 

综观图a~o,我们回发现,整个函数过程所用到的最大栈数目就是3,这也就不难理解第一条语句 .maxstack  3  了。

现在,还有一点让人迷惑的是为什么要引入变量V_3?如上例中,倒数第二条指令ldloc.3 也可以由ldloc.2 代替,因为我们所要的结果就是存储在第二个变量r中的,这不是浪费空间嘛。要注意了,并不所有的返回值都保存在局部变量中的。

有可能我们将参数直接返回,或者将类成员变量返回,如:

              public int Laxi(int x)

              {

                     return x;         //直接将参数返回

              }

或者是  int age;...

        public int GetAge()

        {

                     return age;      //返回在类中定义的字段

        }

或者是直接返回一个表达式:

              public int GetInteger()

              {

                     return age+4*6/2;

              }

则必须进行这一步转换,以return r为例:ldloc.x -> stloc.y ->ldloc.y ->ret . 因为在以上三种情况当中,返回值都不存储在局部变量当中。

 

IL代码底层运行机制

刘强    Cambest@sohu.com2003年5月8日        大家都知道,和Java一样,C#也是基于堆栈的语言。也许对一般人来说,底层的运行细节并不是很重要;但了解这些,对我们理解、...
  • zhoubl668
  • zhoubl668
  • 2009年06月25日 23:03
  • 854

IL代码底层运行机制之

  • zgqtxwd
  • zgqtxwd
  • 2008年04月24日 11:20
  • 119

IL代码底层运行机制之函数相关

IL代码底层运行机制之
  • cambest
  • cambest
  • 2003年11月27日 00:29
  • 1491

IL代码底层运行机制之循环处理

IL代码底层运行机制之
  • cambest
  • cambest
  • 2003年10月28日 23:50
  • 1322

读懂IL代码就这么简单 (一)

转自 http://www.lupaworld.com/article-229958-1.html 对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道...
  • cp790621656
  • cp790621656
  • 2015年06月20日 14:01
  • 1673

轻松读懂IL

学习一下IL指令,把有用的东西分享给大家。
  • xiaouncle
  • xiaouncle
  • 2016年10月10日 10:40
  • 1068

【Unity优化】我所理解的IL指令

我所理解的IL指令,这里目前只列举了一些常见指令,box、unbox、newobj、ldc、stloc等等,我认为比较重要的指令,学习Unity最好是要深刻了解C#较为底层的IL实现。...
  • AndrewFan
  • AndrewFan
  • 2017年03月02日 21:39
  • 1793

IL文件修改提高篇

IL文件修改提高篇 ================================== Object:    熟悉强名字签名之后的代码处理 =============================...
  • buguyiqie
  • buguyiqie
  • 2009年05月08日 22:45
  • 1973

快速启动ILDasm查看当前项目的IL代码

最近在研究IL代码,所以经常在VS中写了C#代码,需要用ILDasm查看下它的IL代码,但每次手工打开实在麻烦,所以这里推荐下使用外部程序:依次打开菜单“工具”-“外部工具”,在“外部工具”对话框中点...
  • bclz_vs
  • bclz_vs
  • 2011年06月15日 16:26
  • 1314

C# 如何查看源程序的IL代码

1、打开microsoft  visual  studio  2008  /  visual  studio  tools /  visual  studio  2008 命令提示  ,并输入ilda...
  • susan19890313
  • susan19890313
  • 2011年11月08日 20:02
  • 9573
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:IL代码底层运行机制
举报原因:
原因补充:

(最多只允许输入30个字)