“nop dword ptr [rax+rax]”是x64汇编指令,它的作用是什么?

25

我正在尝试理解编译器所进行的x64汇编优化。

我使用Windows 8.1上的Visual Studio 2008 SP1 IDE将一个小的C++项目编译为Release版本。

其中一行包含以下汇编代码:

B8 31 00 00 00   mov         eax,31h
0F 1F 44 00 00   nop         dword ptr [rax+rax]

这里是截图:

在此输入图片描述

据我所知,nop 本身就是 什么也不做,但我从未见过带有这样操作数的情况。

有人能解释一下它是做什么的吗?


11
这是一个多字节的NOP指令。指令集参考手册对此进行了解释:http://www.felixcloutier.com/x86/NOP.html - Michael Petch
7
通常用于内存对齐。经常在循环之前使用,以将它们对齐到16或32字节的边界上(16通常是默认值)。这可以提高循环的性能。 - Michael Petch
5
如果您查看地址7ff673c0146b,则是NOP指令的开头。它有5个字节,因此NOP之后的指令将从对齐到16字节的7ff673c01470开始。下一个指令很有可能就是循环的开头。 - Michael Petch
2
@MichaelPetch:是的,你说得对。好知道。谢谢! - c00000fd
3
可能是什么更快:JMP还是一系列NOP? 的重复问题。 - Raymond Chen
显示剩余5条评论
3个回答

28
在本页面的其他评论中,Michael Petch指出了一个网页,描述了Intel x86多字节NOP操作码。该页面有一张有用信息的表格,但不幸的是HTML混乱无法阅读。以下是该页面的一些信息,以及该表格的可读形式:
多字节NOP指令是一种“无操作”指令,用于创建由多个字节组成的“无操作”序列。单字节NOP指令实际上是XCHG(E)AX,(E)AX指令的别名助记符。在支持的处理器上,多字节NOP指令不执行操作;在不支持该指令的处理器上,它会生成未定义的操作码异常。针对需要多字节NOP的情况,在32位和64位模式下建议使用以下操作http://www.felixcloutier.com/x86/NOP.html:(在64位模式下,将eax替换为rax)
请注意,选择正确的字节序列的技术(因此所需的总大小)可能因您使用的汇编器而异。
例如,从表中获取的以下两行汇编代码表面上相似:
nop dword ptr [eax + 00h]
nop dword ptr [eax + 00000000h]

这些只有前导零的数量不同,某些汇编器可能很难禁用它们“有用”的功能,即始终对最短的字节序列进行编码,这可能会使第二个表达式无法访问。
对于多字节NOP情况,您不希望使用此“帮助”,因为您需要确保实际获得所需的字节数。因此,问题是如何仅通过指令助记符指定 mod r / m 位的确切组合,以获得所需的 disp 大小。这个主题很复杂,当然超出了我的知识范围,但Scaled IndexingMOD + R / MSIB可能是一个起点。
现在,正如我知道你正在思考的那样,如果您发现通过指令助记符强制汇编器合作变得困难或不可能,您总是可以转而使用 db (“定义字节”)作为简单的无忧替代方案,这是,嗯,保证可以工作的。

1
我应该澄清一下我的先前评论,这是存档聊天记录中的最后一条评论;如果没有代码路径导致它们,我不确定为什么对齐填充序列需要首先被CPU解释。否则,用db 90 90 90...来填充不是完全可以吗?实际上,这样做还可以防止出现错误的jmp跳转。 - Glenn Slayden
根据汇编器的不同,它并不知道填充指令是否会被执行。但在函数之间,MSVC使用重复的 0xcc (int3) 填充。如果您知道某个地方不应该执行,那么陷阱比默默地跳过进入下一个函数更有意义。 - Peter Cordes
@PeterCordes 哎呀!我的错;我本来想说的是 CC 而不是 90。这很尴尬,因为我之前在这里已经讨论过这个问题了... - Glenn Slayden
那么,它是做什么的? - huang
@JoeHuang NOP 意思是“无指令”。对于微处理器来说,它是一条不执行任何操作的指令。 - Glenn Slayden
显示剩余3条评论

13

如评论所指出的那样,这是一个多字节NOP,通常用于将后续指令对齐到16字节边界,特别是当该指令是循环中的第一条指令时。

这种对齐可以帮助提高指令获取带宽,因为指令获取通常以16个字节为单位进行,因此将循环顶部对齐可以最大程度地减少解码过程中的瓶颈。

这种对齐的重要性可能不如以前那么重要,因为引入了循环缓冲区微操作缓存,它们对对齐的敏感度较低。在某些情况下,这种优化甚至可能导致性能下降,特别是当循环执行次数非常少时。


1
当使用跳转指令从更大的地址跳转到较小的地址时(0EBh XX - jmp short和0E9h XX XX XX XX - jmp near),这段代码对齐将会被执行,其中XX在两种情况下都是有符号负数。因此,编译器将该代码块对齐到10h字节边界以执行跳转。这将提高优化和代码执行速度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接