以分号结尾的空代码行是否等同于 asm("nop") 指令?
不,当然不是。你可以自己简单地尝试一下。(在您自己的机器上,或在 Godbolt 编译器资源管理器上,https: //godbolt.org/ )
FOO(x);
如果扩展为仅仅;
因为FOO()
在这种情况下的适当定义是空字符串,您不会希望无辜的 CPP 宏引入 NOP 。
__nop()
不是库函数。这是一个内在的东西,可以完全按照您的意愿行事。 例如
#ifdef USE_NOP
#ifdef _MSC_VER
#include <intrin.h>
#define NOP() __nop() // _emit 0x90
#else
// assume __GNUC__ inline asm
#define NOP() asm("nop") // implicitly volatile
#endif
#else
#define NOP() // no NOPs
#endif
int idx(int *arr, int b) {
NOP();
return arr[b];
}
用 Clang7.0 -O3 for x86-64 Linux 编译到这个 asm
idx(int*, int):
nop
movsxd rax, esi # sign extend b
mov eax, dword ptr [rdi + 4*rax]
ret
用 32 位 x86 MSVC 19.16 -O2 -Gv 编译到这个 asm
int idx(int *,int) PROC ; idx, COMDAT
npad 1 ; pad with a 1 byte NOP
mov eax, DWORD PTR [ecx+edx*4] ; __vectorcall arg regs
ret 0
并使用 x64 MSVC 19.16 -O2 -Gv 编译到这个 asm (所有这些都是 Godbolt):
int idx(int *,int) PROC ; idx, COMDAT
movsxd rax, edx
npad 1 ; pad with a 1 byte NOP
mov eax, DWORD PTR [rcx+rax*4] ; x64 __vectorcall arg regs
ret 0
b
有趣的是,到 64 位的符号扩展是在 NOP 之前完成的。显然 x64 MSVC 要求(默认情况下)函数以至少 2 字节或更长的指令开头(在 1 字节push
指令的序言之后,也许?),因此它们支持使用jmp rel8
.
如果你在 1 指令函数中使用它,你会npad 2
npad 1
在x64 MSVC之前得到一个(2 字节 NOP) :
int bar(int a, int b) {
__nop();
return a+b;
}
;; x64 MSVC 19.16
int bar(int,int) PROC ; bar, COMDAT
npad 2
npad 1
lea eax, DWORD PTR [rcx+rdx]
ret 0
我不确定 MSVC 对纯寄存器指令的 NOP 重新排序有多积极,但a^=b;
在NOP 指令之前__nop()
实际上会导致。xor ecx, edx
但是wrt。内存访问,MSVC 决定在这种情况下不重新排序任何东西来填充那个 2 字节的插槽。
int sink;
int foo(int a, int b) {
__nop();
sink = 1;
//a^=b;
return a+b;
}
;; MSVC 19.16 -O2
int foo(int,int) PROC ; foo, COMDAT
npad 2
npad 1
lea eax, DWORD PTR [rcx+rdx]
mov DWORD PTR int sink, 1 ; sink
ret 0
__nop()
它首先执行 LEA,但在;之前不移动它。似乎明显错过了优化,但是如果您要插入__nop()
指令,那么优化显然不是优先事项。
如果你编译成.obj
or.exe
并反汇编,你会看到一个普通的0x90 nop
. 但不幸的是,Godbolt 不支持 MSVC,只支持 Linux 编译器,所以我所能做的就是复制 asm 文本输出。
正如您所期望的那样,在__nop()
ifdefed 退出后,函数可以正常编译为相同的代码,但没有npad
指令。
该nop
指令将运行与 NOP() 宏在 C 抽象机中运行的次数一样多。 订购wrt。volatile
优化器或 wrt 不保证周围的非内存访问。寄存器中的计算。
如果您希望它成为编译时内存重新排序屏障,对于 GNU C,请使用 asm("nop" ::: "memory");`。对于 MSVC,我认为这必须是分开的。