我正在通过MSIL并注意到有很多nop指令.MSDN文章称,如果操作码被修补,它们不采取任何操作并用于填充空间.它们在调试版本中比在发布版本中使用得更多.我知道这些语句用于汇编语言,以确保操作码适合字边界,但为什么MSIL需要它?
NOP有多种用途:
它们允许调试器在一条线上放置断点,即使它与生成的代码中的其他组合在一起.
它允许加载器使用不同大小的目标偏移来修补跳转.
它允许一个代码块在特定边界对齐,这对缓存很有用.
它允许增量链接以通过调用新部分来覆盖代码块,而不必担心整体功能改变大小.
以下是调试使用nops的方法:
语言编译器(C#,VB等)使用Nops来定义隐式序列点.这些告诉JIT编译器在哪里确保机器指令可以映射回IL指令.
Rick Byer关于DebuggingModes.IgnoreSymbolStoreSequencePoints的博客文章解释了一些细节.
C#还会在调用指令后放置Nops,以便源中的返回站点位置是呼出而不是呼叫后的线路.
它为代码提供了基于行的标记(例如断点)的机会,其中发布版本不会发出任何内容.
在针对特定处理器或体系结构进行优化时,它还可以使代码运行得更快:
处理器长时间使用大致并行工作的多个流水线,因此可以同时完成两个独立的指令.在具有两个流水线的简单处理器上,第一个可以支持所有指令,而第二个仅支持一个子集.此外,当必须等待先前尚未完成的指令的结果时,管道之间存在一些停顿.
在这些情况下,专用nop可以强制下一个指令进入特定流水线(第一个或不是第一个),并改进后续指令的配对,使得nop的成本 大于摊销.
杜德!无操作真棒!这是一条只消耗时间的指令。在昏暗的黑暗时代,您可以使用它来对关键循环中的时序进行微调整,或者更重要的是,它可以用作自我修改代码中的填充物。
在最近(四年)工作的一个处理器中,使用NOP来确保上一个操作在下一个操作开始之前已完成。例如:
将值加载到寄存器(需要8个周期)nop 8将1加到寄存器
确保在进行加法操作之前寄存器具有正确的值。
另一个用途是填写执行单元,例如中断向量必须为一定大小(32字节),因为向量0的地址为0,向量1的地址为0x20,依此类推,因此编译器将NOP放入其中需要。