是一个空行代码,以分号等同于asm(“nop”)指令结束?
volatile int x = 5;
if(x == 5){
printf("x has not been changed yet\n");
}
else{
;//Is this the same as asm("nop") or __asm nop in windows?
//alternatively could use __asm nop or __nop();
}
我看了这个答案,这让我不想使用x86特定的内联汇编实现。 Is `__asm nop` the Windows equivalent of `asm volatile("nop");` from GCC compiler
我可以使用这个void __nop(); msdn似乎推荐的功能,但如果我不需要,我不想在库中拖动。 https://docs.microsoft.com/en-us/cpp/intrinsics/nop?view=vs-2017
是否有一种廉价,可移植的方式来添加一个不会被编译出来的nop指令?我认为一个空的分号要么是nop还是编译出来但我今晚因为某些原因无法找到任何信息。
澄清编辑我可以使用内联asm为x86执行此操作,但我希望它是可移植的。我可以使用windows库__nop(),但我不想将库导入我的项目,这是不受欢迎的开销。
我正在寻找一种生成NOP指令的切割方法,该指令不会被优化(最好使用标准C语法),可以制作成MACRO并在整个项目中使用,具有最小的开销和工作(或者可以很容易地改进到在windows / linux / x86 / x64上工作)。
谢谢。
我的意思是我不想添加库只是为了强制编译器添加NOP。
...以一种独立于编译器设置(例如优化设置)的方式,并且以适用于所有Visual C ++版本(甚至可能是其他编译器)的方式:
没有机会:只要汇编程序代码具有C代码描述的行为,编译器就可以自由地生成代码。
并且因为NOP
指令不会改变程序的行为,所以编译器可以自由添加它或将其保留。
即使您找到了强制编译器生成NOP
的方法:编译器的一次更新或修改某个文件的Windows更新,编译器可能不再生成NOP
指令。
我可以使用内联asm为x86执行此操作,但我希望它是可移植的。
正如我上面所写,任何强制编译器编写NOP
的方法只能用于某个CPU的某个编译器版本。
使用内联汇编或__nop()
,您可能会涵盖某个制造商的所有编译器(例如:所有GNU C编译器或Visual C ++的所有变体等...)。
另一个问题是:你明确需要“官方”NOP
指令,还是你可以接受任何无效的指令?
如果你可以接受任何指令(几乎没有),那么读取全局或静态volatile
变量可以替代NOP
:
static volatile char dummy;
...
else
{
(void)dummy;
}
这应该强制编译器添加一个MOV
指令读取变量dummy
。
背景:
如果您编写了设备驱动程序,则可以将变量dummy
链接到读取变量具有“副作用”的某个位置。示例:读取位于VGA视频内存中的变量会影响屏幕内容!
使用volatile
关键字,您不仅告诉编译器变量的值可能随时发生变化,而且读取变量可能会产生这样的影响。
这意味着编译器必须假定不读取变量会导致程序无法正常工作。它无法优化(实际上不必要的)MOV
指令读取变量。
是一个空行代码,以分号等同于asm(“nop”)指令结束?
不,当然不。你可以自己尝试一下。 (在您自己的机器上,或在Godbolt编译器资源管理器上,https://godbolt.org/)
如果FOO(x);
扩展到;
,你不希望无辜的CPP宏引入NOP,因为在这种情况下FOO()
的适当定义是空字符串。
__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为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 for all of them):
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指令函数中使用它,则在x64 MSVC的npad 2
之前得到npad 1
(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
它首先执行LEA,但不会在__nop()
之前移动它;似乎是一个明显错过的优化,但如果你插入__nop()
指令然后再次优化显然不是优先级。
如果你编译成.obj
或.exe
并拆卸,你会看到一个普通的0x90 nop
。但不幸的是,Godbolt不支持MSVC,只支持Linux编译器,因此我可以轻松完成复制asm文本输出。
正如你所期望的那样,在__nop()
ifdefed out的情况下,函数正常编译,编译相同但没有npad
指令。
nop
指令将运行NOP()宏在C抽象机器中执行的次数。订购了。优化器或wrt无法保证周围的非volatile
内存访问。寄存器中的计算。
如果你想让它成为编译时内存重新排序的障碍,对于GNU C,请使用asm(“nop”:::“memory”);`。对于MSVC,我认为这必须是分开的。