是否有空的代码行以等价的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
我可以使用这个无效的__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上进行改进。
谢谢。
答案 0 :(得分:3)
是否有空的代码行以等价的asm(“ nop”)指令结尾?
不,当然不是。您可以自己尝试一下。 (在您自己的计算机上,或者在Godbolt编译器浏览器上,https://godbolt.org/)
如果FOO(x);
扩展为;
,则您不希望无辜的CPP宏引入NOP,因为在这种情况下,FOO()
的适当定义是空字符串。
__nop()
不是库函数。这是完全符合您想要的功能的 int 。例如
#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];
}
针对x86-64 Linux使用Clang7.0 -O3编译为该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
指令的序言之后?),因此它们支持使用{{ 1}}。
如果在1指令功能中使用此命令,则会从x64 MSVC中在jmp rel8
之前获得npad 2
(2字节NOP):
npad 1
int bar(int a, int b) {
__nop();
return a+b;
}
我不确定MSVC将如何针对纯寄存器指令对NOP进行重新排序,但是在;; x64 MSVC 19.16
int bar(int,int) PROC ; bar, COMDAT
npad 2
npad 1
lea eax, DWORD PTR [rcx+rdx]
ret 0
之后的a^=b;
实际上会导致__nop()
之前 NOP指令。
但是wrt。内存访问,在这种情况下,MSVC决定不重新排序任何东西以填充该2字节的插槽。
xor ecx, edx
int sink;
int foo(int a, int b) {
__nop();
sink = 1;
//a^=b;
return a+b;
}
它首先执行LEA,但不会在;; 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()
指令,那么显然不是优先考虑的地方。
如果您编译为__nop()
或.obj
并进行反汇编,则会看到普通的.exe
。但是不幸的是,Godbolt不支持MSVC,只有Linux编译器,所以我能做的就是复制asm文本输出。
正如您所期望的那样,使用0x90 nop
进行扩展时,函数可以正常编译为相同的代码,但没有__nop()
指令。
npad
指令的运行次数与C抽象机中NOP()宏的运行次数相同。订购wrt。优化器或wrt无法保证周围的非nop
内存访问。寄存器中的计算。
如果希望它成为编译时内存的重新排序障碍,则对于GNU C,请使用asm(“ nop” :::“ memory”);`。我认为对于MSVC,这必须分开。
答案 1 :(得分:3)
我的意思是我不想添加一个库来强制编译器添加一个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
指令来读取变量。