当前位置:  开发笔记 > 编程语言 > 正文

添加一个int变量时生成不同的IL

如何解决《添加一个int变量时生成不同的IL》经验,为你挑选了2个好方法。

我在c#中有这个程序:

using System;

class Program
{
    public static void Main()
    {
    int i = 4;
    double d = 12.34;
    double PI = Math.PI;
    string name = "Ehsan";


    }
}

当我编译它时,以下是编译器为Main生成的IL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       30 (0x1e)
  .maxstack  1
  .locals init (int32 V_0,
           float64 V_1,
           float64 V_2,
           string V_3)
  IL_0000:  nop
  IL_0001:  ldc.i4.4
  IL_0002:  stloc.0
  IL_0003:  ldc.r8     12.34
  IL_000c:  stloc.1
  IL_000d:  ldc.r8     3.1415926535897931
  IL_0016:  stloc.2
  IL_0017:  ldstr      "Ehsan"
  IL_001c:  stloc.3
  IL_001d:  ret
} // end of method Program::Main

这很好,我理解它,现在如果我添加另一个整数变量然后生成不同的东西,这里是修改后的c#代码:

using System;

class Program
{
    public static void Main()
    {
    int unassigned;
    int i = 4;
    unassigned = i;
    double d = 12.34;
        double PI = Math.PI;
    string name = "Ehsan";


    }
}

这是针对上面的c#代码生成的IL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       33 (0x21)
  .maxstack  1
  .locals init (int32 V_0,
           int32 V_1,
           float64 V_2,
           float64 V_3,
           string V_4)
  IL_0000:  nop
  IL_0001:  ldc.i4.4
  IL_0002:  stloc.1
  IL_0003:  ldloc.1
  IL_0004:  stloc.0
  IL_0005:  ldc.r8     12.34
  IL_000e:  stloc.2
  IL_000f:  ldc.r8     3.1415926535897931
  IL_0018:  stloc.3
  IL_0019:  ldstr      "Ehsan"
  IL_001e:  stloc.s    V_4  // what is happening here in this case
  IL_0020:  ret
} // end of method Program::Main

如果您现在注意到stloc.s生成的语句是V_4本地的,但我不清楚这一点,我也没有得到这些本地人的目的是什么,我的意思是:

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

Jon Hanna.. 5

有些事情需要注意.

首先,这可能是一个调试版本,或者至少在编译中关闭了某些优化.我期望在这里看到的是:

.method public hidebysig static void Main () cil managed 
{
  .entrypoint

  IL_0000: ret
}

也就是说,由于没有使用那些本地人,我希望编译器完全跳过它们.它不会在调试版本中,但这是一个很好的例子,说明C#所说的内容与IL所说的内容之间存在相当大的差异.

接下来要注意的是IL方法的结构.您有一个本地值数组,使用.locals各种类型的块定义.这些通常与C#的内容非常接近,尽管通常会有捷径和重新安排.

最后,我们有一组指令,这些指令都作用于那些本地,任何参数和它可以推送的堆栈,它可以从中弹出,并且各种指令将在其上进行交互.

接下来要注意的是,你在这里看到的IL是一种字节码的汇编:这里的每条指令都有一对一映射到一个或两个字节,每个值也消耗一定数量的字节.因此,例如,stloc V_4(实际并不存在于你的例子,但我们会得出这样)将映射到0xFE 0x0E 0x04 0x00这里0xFE 0x0E是进行编码stloc,并0x04 0x00认为的4这是有问题的地方的索引.这意味着"弹出堆栈顶部的值,并将其存储在第5个(索引4)本地".

现在,这里有一些缩写.其中之一是.s几个指令的"短"形式(_S以等效System.Reflection.Emit.OpCode值的名义).这些是采用单字节值(有符号或无符号取决于指令)的其他指令的变体,其中另一种形式采用两个或四个字节的值,通常是索引或跳转的相对距离.因此,stloc V_4我们不能stloc.s V_4只拥有哪个0x13 0x4,而且更小.

然后有一些变体包含指令中的特定值.因此,而不是要么stloc V_0或者stloc.s V_0我们可以只使用stloc.0这仅仅是单字节0x0A.

这使得很多的感觉,当你认为它是常见的只能有一个时间的当地人使用了一把,所以无论使用stloc.s或(更好的)这样的人stloc.0,stloc.1等)给出了微小的储蓄加起来相当许多.

但只有这么多.如果我们有如stloc.252,stloc.253等再有会出现很多这样的指令,并且将必须更加需要每个指令的字节数,并会全面亏损.本地相关(stloc,ldloc)和参数相关(ldarg)的超短形式只能达到3.(有一种stargstarg.s,但没有starg.0等为存储到参数是相对罕见的).ldc.i4/ ldc.i4.s(推恒定32位有符号值压入堆栈)具有超级短版本从去ldc.i4.0ldc.i4.8和也lcd.i4.m1-1.

值得注意的是,V_4您的代码中根本不存在.无论你检查什么IL都不知道你使用了变量名,name所以它只是用它V_4.(你在使用什么,BTW?我大部分都使用ILSpy,如果你调试与文件相关的信息,它会相应地调用它name).

因此,要生成具有更多可比名称的方法的注释非短路版本,我们可以编写以下CIL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  .maxstack  1
  .locals init (int32 unassigned,
           int32 i,
           float64 d,
           float64 PI,
           string name)
  nop                           // Do Nothing (helps debugger to have some of these around).
  ldc.i4   4                    // Push number 4 on stack
  stloc    i                    // Pop value from stack, put in i (i = 4)
  ldloc    i                    // Push value in i on stack
  stloc    unassigned           // Pop value from stack, put in unassigned (unassigned = i)
  ldc.r8   12.34                // Push the 64-bit floating value 12.34 onto the stack
  stloc    d                    // Push the value on stack in d (d = 12.34)
  ldc.r8   3.1415926535897931   // Push the 64-bit floating value 3.1415926535897931 onto the stack.
  stloc PI                      // Pop the value from stack, put in PI (PI = 3.1415… which is the constant Math.PI)
  ldstr    "Ehsan"              // Push the string "Ehsan" on stack
  stloc    name                 // Pop the value from stack, put in name
  ret                           // return.
}

这将与您的代码一样,但有点大.所以我们更换stlocstloc.0...... stloc.3在这里我们可以,stloc.s我们不能用那些,但仍然可以使用stloc.s,并且ldc.i4 4ldc.i4.4,我们将有更短的字节代码做同样的事情:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  .maxstack  1
  .locals init (int32 unassigned,
           int32 i,
           float64 d,
           float64 PI,
           string name)
  nop                           // Do Nothing (helps debugger to have some of these around).
  ldc.i4.4                      // Push number 4 on stack
  stloc.1                       // Pop value from stack, put in i (i = 4)
  ldloc.1                       // Push value in i on stack
  stloc.0                       // Pop value from stack, put in unassigned (unassigned = i)
  ldc.r8   12.34                // Push the 64-bit floating value 12.34 onto the stack
  stloc.2                       // Push the value on stack in d (d = 12.34)
  ldc.r8   3.1415926535897931   // Push the 64-bit floating value 3.1415926535897931 onto the stack.
  stloc.3                       // Pop the value from stack, put in PI (PI = 3.1415… which is the constant Math.PI)
  ldstr    "Ehsan"              // Push the string "Ehsan" on stack
  stloc.s  name                 // Pop the value from stack, put in name
  ret                           // return.
}

而现在我们的反汇编代码完全相同,只是我们有更好的名字.请记住,名称不会出现在字节代码中,因此反汇编程序无法做到尽可能好.


你在评论中提出的问题应该是另一个问题,但它提供了一个添加重要内容的机会,我在上面只是简单地提到过.我们考虑一下:

public static void Maybe(int a, int b)
{
  if (a > b)
    Console.WriteLine("Greater");
  Console.WriteLine("Done");
}

在调试中编译,最终得到如下内容:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  IL_0000: nop
  IL_0001: ldarg.0
  IL_0002: ldarg.1
  IL_0003: cgt
  IL_0005: ldc.i4.0
  IL_0006: ceq
  IL_0008: stloc.0
  IL_0009: ldloc.0
  IL_000a: brtrue.s IL_0017

  IL_000c: ldstr "Greater"
  IL_0011: call void [mscorlib]System.Console::WriteLine(string)
  IL_0016: nop

  IL_0017: ldstr "Done"
  IL_001c: call void [mscorlib]System.Console::WriteLine(string)
  IL_0021: nop
  IL_0022: ret
}

现在要注意的一点是,所有标签IL_0017等都会根据指令的索引添加到每一行.这使得反汇编程序的生活更加轻松,但除非跳转到标签,否则不一定非常必要.让我们删除所有未跳转到的标签:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  nop
  ldarg.0
  ldarg.1
  cgt
  ldc.i4.0
  ceq
  stloc.0
  ldloc.0
  brtrue.s IL_0017

  ldstr "Greater"
  call void [mscorlib]System.Console::WriteLine(string)
  nop

  IL_0017: ldstr "Done"
  call void [mscorlib]System.Console::WriteLine(string)
  nop
  ret
}

现在,让我们考虑每行的作用:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  nop                   // Do nothing
  ldarg.0               // Load first argument (index 0) onto stack.
  ldarg.1               // Load second argument (index 1) onto stack.
  cgt                   // Pop two values from stack, push 1 (true) if the first is greater
                        // than the second, 0 (false) otherwise.
  ldc.i4.0              // Push 0 onto stack.
  ceq                   // Pop two values from stack, push 1 (true) if the two are equal,
                        // 0 (false) otherwise.
  stloc.0               // Pop value from stack, store in first local (index 0)
  ldloc.0               // Load first local onto stack.
  brtrue.s IL_0017      // Pop value from stack. If it's non-zero (true) jump to IL_0017

  ldstr "Greater"       // Load string "Greater" onto stack.

                        // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  nop                   // Do nothing

  IL_0017: ldstr "Done" // Load string "Done" onto stack.
                        // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  nop                   // Do nothing
  ret                   // return
}

让我们以非常直接的方式将它写回C#中:

public static void Maybe(int a, int b)
{
  bool shouldJump = (a > b) == false;
  if (shouldJump) goto IL_0017;
  Console.WriteLine("Greater");
IL_0017:
  Console.WriteLine("Done");
}

尝试一下,你会发现它做同样的事情.使用的goto是因为CIL实际上并没有类似的东西for或者while甚至阻挡我们可以把一个后if或者else,它只是有跳跃和条件跳转.

但是为什么还要存储价值(我shouldJump在C#重写中调用的内容)而不仅仅是对它进行操作?

如果您正在调试,只是为了更容易检查每个点上发生了什么.特别是,为了使调试器能够在a > b已经解决但尚未执行的位置停止,a > b或者a <= b需要存储其相反的().

由于这个原因,调试版本倾向于编写CIL,花费大量时间来编写它刚才做的记录.有了发布版本,我们会得到更像:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  ldarg.0           // Load first argument onto stack
  ldarg.1           // Load second argument onto stack
  ble.s IL_000e     // Pop two values from stack. If the first is
                    // less than or equal to the second, goto IL_000e: 
  ldstr "Greater"   // Load string "Greater" onto stack.
                    // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
                    // Load string "Done" onto stack.
  IL_000e: ldstr "Done"
                    // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  ret
}

或者做一个类似的逐行写回C#:

public static void Maybe(int a, int b)
{
  if (a <= b) goto IL_000e;
  Console.WriteLine("Greater");
IL_000e:
  Console.WriteLine("Done");
}

因此,您可以看到发布版本如何更简洁地执行相同的操作.



1> Jon Hanna..:

有些事情需要注意.

首先,这可能是一个调试版本,或者至少在编译中关闭了某些优化.我期望在这里看到的是:

.method public hidebysig static void Main () cil managed 
{
  .entrypoint

  IL_0000: ret
}

也就是说,由于没有使用那些本地人,我希望编译器完全跳过它们.它不会在调试版本中,但这是一个很好的例子,说明C#所说的内容与IL所说的内容之间存在相当大的差异.

接下来要注意的是IL方法的结构.您有一个本地值数组,使用.locals各种类型的块定义.这些通常与C#的内容非常接近,尽管通常会有捷径和重新安排.

最后,我们有一组指令,这些指令都作用于那些本地,任何参数和它可以推送的堆栈,它可以从中弹出,并且各种指令将在其上进行交互.

接下来要注意的是,你在这里看到的IL是一种字节码的汇编:这里的每条指令都有一对一映射到一个或两个字节,每个值也消耗一定数量的字节.因此,例如,stloc V_4(实际并不存在于你的例子,但我们会得出这样)将映射到0xFE 0x0E 0x04 0x00这里0xFE 0x0E是进行编码stloc,并0x04 0x00认为的4这是有问题的地方的索引.这意味着"弹出堆栈顶部的值,并将其存储在第5个(索引4)本地".

现在,这里有一些缩写.其中之一是.s几个指令的"短"形式(_S以等效System.Reflection.Emit.OpCode值的名义).这些是采用单字节值(有符号或无符号取决于指令)的其他指令的变体,其中另一种形式采用两个或四个字节的值,通常是索引或跳转的相对距离.因此,stloc V_4我们不能stloc.s V_4只拥有哪个0x13 0x4,而且更小.

然后有一些变体包含指令中的特定值.因此,而不是要么stloc V_0或者stloc.s V_0我们可以只使用stloc.0这仅仅是单字节0x0A.

这使得很多的感觉,当你认为它是常见的只能有一个时间的当地人使用了一把,所以无论使用stloc.s或(更好的)这样的人stloc.0,stloc.1等)给出了微小的储蓄加起来相当许多.

但只有这么多.如果我们有如stloc.252,stloc.253等再有会出现很多这样的指令,并且将必须更加需要每个指令的字节数,并会全面亏损.本地相关(stloc,ldloc)和参数相关(ldarg)的超短形式只能达到3.(有一种stargstarg.s,但没有starg.0等为存储到参数是相对罕见的).ldc.i4/ ldc.i4.s(推恒定32位有符号值压入堆栈)具有超级短版本从去ldc.i4.0ldc.i4.8和也lcd.i4.m1-1.

值得注意的是,V_4您的代码中根本不存在.无论你检查什么IL都不知道你使用了变量名,name所以它只是用它V_4.(你在使用什么,BTW?我大部分都使用ILSpy,如果你调试与文件相关的信息,它会相应地调用它name).

因此,要生成具有更多可比名称的方法的注释非短路版本,我们可以编写以下CIL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  .maxstack  1
  .locals init (int32 unassigned,
           int32 i,
           float64 d,
           float64 PI,
           string name)
  nop                           // Do Nothing (helps debugger to have some of these around).
  ldc.i4   4                    // Push number 4 on stack
  stloc    i                    // Pop value from stack, put in i (i = 4)
  ldloc    i                    // Push value in i on stack
  stloc    unassigned           // Pop value from stack, put in unassigned (unassigned = i)
  ldc.r8   12.34                // Push the 64-bit floating value 12.34 onto the stack
  stloc    d                    // Push the value on stack in d (d = 12.34)
  ldc.r8   3.1415926535897931   // Push the 64-bit floating value 3.1415926535897931 onto the stack.
  stloc PI                      // Pop the value from stack, put in PI (PI = 3.1415… which is the constant Math.PI)
  ldstr    "Ehsan"              // Push the string "Ehsan" on stack
  stloc    name                 // Pop the value from stack, put in name
  ret                           // return.
}

这将与您的代码一样,但有点大.所以我们更换stlocstloc.0...... stloc.3在这里我们可以,stloc.s我们不能用那些,但仍然可以使用stloc.s,并且ldc.i4 4ldc.i4.4,我们将有更短的字节代码做同样的事情:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  .maxstack  1
  .locals init (int32 unassigned,
           int32 i,
           float64 d,
           float64 PI,
           string name)
  nop                           // Do Nothing (helps debugger to have some of these around).
  ldc.i4.4                      // Push number 4 on stack
  stloc.1                       // Pop value from stack, put in i (i = 4)
  ldloc.1                       // Push value in i on stack
  stloc.0                       // Pop value from stack, put in unassigned (unassigned = i)
  ldc.r8   12.34                // Push the 64-bit floating value 12.34 onto the stack
  stloc.2                       // Push the value on stack in d (d = 12.34)
  ldc.r8   3.1415926535897931   // Push the 64-bit floating value 3.1415926535897931 onto the stack.
  stloc.3                       // Pop the value from stack, put in PI (PI = 3.1415… which is the constant Math.PI)
  ldstr    "Ehsan"              // Push the string "Ehsan" on stack
  stloc.s  name                 // Pop the value from stack, put in name
  ret                           // return.
}

而现在我们的反汇编代码完全相同,只是我们有更好的名字.请记住,名称不会出现在字节代码中,因此反汇编程序无法做到尽可能好.


你在评论中提出的问题应该是另一个问题,但它提供了一个添加重要内容的机会,我在上面只是简单地提到过.我们考虑一下:

public static void Maybe(int a, int b)
{
  if (a > b)
    Console.WriteLine("Greater");
  Console.WriteLine("Done");
}

在调试中编译,最终得到如下内容:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  IL_0000: nop
  IL_0001: ldarg.0
  IL_0002: ldarg.1
  IL_0003: cgt
  IL_0005: ldc.i4.0
  IL_0006: ceq
  IL_0008: stloc.0
  IL_0009: ldloc.0
  IL_000a: brtrue.s IL_0017

  IL_000c: ldstr "Greater"
  IL_0011: call void [mscorlib]System.Console::WriteLine(string)
  IL_0016: nop

  IL_0017: ldstr "Done"
  IL_001c: call void [mscorlib]System.Console::WriteLine(string)
  IL_0021: nop
  IL_0022: ret
}

现在要注意的一点是,所有标签IL_0017等都会根据指令的索引添加到每一行.这使得反汇编程序的生活更加轻松,但除非跳转到标签,否则不一定非常必要.让我们删除所有未跳转到的标签:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  nop
  ldarg.0
  ldarg.1
  cgt
  ldc.i4.0
  ceq
  stloc.0
  ldloc.0
  brtrue.s IL_0017

  ldstr "Greater"
  call void [mscorlib]System.Console::WriteLine(string)
  nop

  IL_0017: ldstr "Done"
  call void [mscorlib]System.Console::WriteLine(string)
  nop
  ret
}

现在,让我们考虑每行的作用:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  .maxstack 2
  .locals init (
    [0] bool CS$4$0000
  )

  nop                   // Do nothing
  ldarg.0               // Load first argument (index 0) onto stack.
  ldarg.1               // Load second argument (index 1) onto stack.
  cgt                   // Pop two values from stack, push 1 (true) if the first is greater
                        // than the second, 0 (false) otherwise.
  ldc.i4.0              // Push 0 onto stack.
  ceq                   // Pop two values from stack, push 1 (true) if the two are equal,
                        // 0 (false) otherwise.
  stloc.0               // Pop value from stack, store in first local (index 0)
  ldloc.0               // Load first local onto stack.
  brtrue.s IL_0017      // Pop value from stack. If it's non-zero (true) jump to IL_0017

  ldstr "Greater"       // Load string "Greater" onto stack.

                        // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  nop                   // Do nothing

  IL_0017: ldstr "Done" // Load string "Done" onto stack.
                        // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  nop                   // Do nothing
  ret                   // return
}

让我们以非常直接的方式将它写回C#中:

public static void Maybe(int a, int b)
{
  bool shouldJump = (a > b) == false;
  if (shouldJump) goto IL_0017;
  Console.WriteLine("Greater");
IL_0017:
  Console.WriteLine("Done");
}

尝试一下,你会发现它做同样的事情.使用的goto是因为CIL实际上并没有类似的东西for或者while甚至阻挡我们可以把一个后if或者else,它只是有跳跃和条件跳转.

但是为什么还要存储价值(我shouldJump在C#重写中调用的内容)而不仅仅是对它进行操作?

如果您正在调试,只是为了更容易检查每个点上发生了什么.特别是,为了使调试器能够在a > b已经解决但尚未执行的位置停止,a > b或者a <= b需要存储其相反的().

由于这个原因,调试版本倾向于编写CIL,花费大量时间来编写它刚才做的记录.有了发布版本,我们会得到更像:

.method public hidebysig static 
  void Maybe (
    int32 a,
    int32 b
  ) cil managed 
{
  ldarg.0           // Load first argument onto stack
  ldarg.1           // Load second argument onto stack
  ble.s IL_000e     // Pop two values from stack. If the first is
                    // less than or equal to the second, goto IL_000e: 
  ldstr "Greater"   // Load string "Greater" onto stack.
                    // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
                    // Load string "Done" onto stack.
  IL_000e: ldstr "Done"
                    // Call Console.WriteLine(string)
  call void [mscorlib]System.Console::WriteLine(string)
  ret
}

或者做一个类似的逐行写回C#:

public static void Maybe(int a, int b)
{
  if (a <= b) goto IL_000e;
  Console.WriteLine("Greater");
IL_000e:
  Console.WriteLine("Done");
}

因此,您可以看到发布版本如何更简洁地执行相同的操作.



2> Hans Passant..:

MSIL经过大量微优化,使存储尽可能小.转到Opcodes类并记下列出的Stloc说明.它有6个版本,它们都完全相同.

Stloc_0,Stloc_1,Stloc_2Stloc_3是最小的,他们只需要一个字节.他们使用的变量号是隐式的,0到3.当然非常常用.

然后是Stloc_S,它是一个双字节操作码,第二个字节用于编码变量号.当方法具有4个以上的变量时,需要使用此方法.

最后Stloc,它是一个三字节操作码,使用两个字节来编码变量号.当方法具有超过256个变量时必须使用.希望你永远不会那样做.当你编写一个超过65536个变量的怪物时,你运气不好,这是不受支持的.已经完成了btw,自动生成的代码可以超越这个限制.

很容易看到第二个片段中发生了什么,你添加了unassigned变量并将局部变量的数量从4增加到5.由于没有Stloc_4,编译器必须使用Stloc_S来分配第5个变量.

推荐阅读
coco2冰冰
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有