如果您在调试器中打开此函数,并在调试模式下编译代码:
bool foo(string arg)
{
return bar(arg);
}
您可以设置 3 个断点:
- 在函数的左大括号处。
- 在“返回”线上。
- 在函数的右大括号处。
在左大括号上设置断点意味着“在调用此函数时中断”。这就是为什么在方法的开头有一个无操作指令的原因。当断点设置在左大括号上时,调试器实际上将它设置为空操作。
在右大括号上设置断点意味着“此函数退出时中断”。为了实现这一点,函数需要在它的 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,它可以获得使困惑)。所以通常托管代码编译器不会这样做。它做了一些“昂贵的事情”(JIT 不想在运行时做),比如死代码检测和实时变量分析,但它们并没有解决滑动窗口优化解决的问题。