有人可以解释这几行MSIL吗?

时间:2009-05-18 21:56:33

标签: stack branch cil local-variables typechecking

有人可以解释这几行MSIL吗?为什么它会将评估堆栈中的值移动到局部变量,只是立即将其移回并返回?

以下MSIL代码加载单个参数(字符串),调用返回bool的方法,然后返回该bool值。我不明白为什么它调用stloc.0将方法的返回值存储在局部变量中,然后执行显式的无条件控制传输到下一个标记的行(似乎没必要),只是将值右移回到返回之前的评估堆栈。

.maxstack 1
.locals init ([0] bool CS$1$0000)
L_0000: nop
L_0001: ldarg.0
L_0002: call bool FuncNameNotImporant::MethodNameNotImporant(string)
L_0007: stloc.0 
L_0008: br.s L_000a
L_000a: ldloc.0 
L_000b: ret 

我最好猜测为什么会这样做是为了确保评估堆栈上的值在返回之前实际上是一个布尔值。但我对明确跳到下一行是无能为力的;我的意思是,不管怎样它不会去那里?该方法的C#源代码只有一行,它返回方法的结果。

2 个答案:

答案 0 :(得分:7)

如果在调试器中打开此函数,代码在调试模式下编译:

bool foo(string arg)
{
    return bar(arg);
}

您可以设置3个断点:

  1. 在功能的开头支撑。
  2. 在“返回”行。
  3. 在该功能的右大括号中。
  4. 在左大括号上设置断点意味着“在调用此函数时断开”。这就是为什么在方法开头有一个无操作指令的原因。当在开括号上设置断点时,调试器实际上将其设置在无操作上。

    在右括号上设置断点意味着“此函数退出时断开”。为了实现这一点,函数需要在IL中有一条返回指令,其中可以设置断点。编译器通过使用临时变量来存储返回值并转换

    来启用它
    return retVal;
    

    进入

    $retTmp = retVal;
    goto exit;
    

    然后在方法的底部注入以下代码:

    exit:
    return $ret;
    

    此外,在调试模式下,编译器对他们生成的代码很愚蠢。他们基本上做了类似的事情:

    GenerateProlog();
    foreach (var statement in statements)
    {
        Generate(statement);
    }
    GenerateEpilog();
    

    在你的情况下,你看到了:

    return foo(arg);
    

    被翻译成:

    ; //this is a no-op
    bool retTemp = false;
    retTemp = foo(arg);
    goto exit;
    exit:
    return retTemp;
    

    如果编译器正在进行“滑动窗口优化”,它可能能够查看该代码并意识到存在一些冗余。但是,编译器通常不会在调试模式下执行此操作。编译器优化可以执行诸如消除变量和重新排序指令之类的操作,这使得调试变得困难。由于调试版本的目的是启用调试,因此启用优化并不好。

    在发布版本中,代码看起来不像那样。那是因为编译器没有引入特殊代码来在开始和结束括号上启用断点,这只留下以下内容进行编译:

    return bar(arg);
    

    最终看起来很简单。

    然而,需要注意的一点是,我认为C#编译器不会进行大量的滑动窗口优化,即使在零售版本中也是如此。这是因为大多数优化都依赖于底层处理器架构,因此由JIT编译器完成。在C#编译器中进行优化,甚至是与处理器无关的优化,都会妨碍JIT优化代码的能力(它正在寻找由非优化代码生成生成的模式,如果它看到非常优化的IL,它可以获得困惑)。所以通常manged代码编译器不会这样做。它做了一些“昂贵的事情”(JIT不想在运行时做),如死代码检测和实时变量分析,但它们没有解决滑动窗口优化解决的问题。

答案 1 :(得分:4)

您是在编译还是在发布模式下进行编译?在发布模式中,我得到:

.method private hidebysig static bool Test1(string arg) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: call bool FuncNameNotImportant::MethodNameNotImportant(string)
    L_0006: ret 
}

您看到的分支可能是调试器支持。