Является ли пустая строка кода, которая заканчивается точкой с запятой, эквивалентной команде asm ("nop")?
Нет, конечно, нет.Вы могли бы тривиально попробовать это сами.(На вашей собственной машине или в проводнике компилятора Godbolt, https://godbolt.org/)
Вы не хотели бы, чтобы невинные макросы CPP вводили NOP, если FOO(x);
расширен до ;
, потому что соответствующее определение для 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 к этому ассемблеру ( 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
.
Если вы используете tВ функции с 1 инструкцией вы получаете npad 2
(2-байтовый NOP) перед npad 1
от MSVC x64:
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()
фактически приведет к xor ecx, edx
до инструкции NOP.
Но относительно.В этом случае 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 абстрактноммашина. Заказ в отношении.окружающий доступ не к volatile
памяти не гарантируется оптимизатором или относительно него.вычисления в регистрах.
Если вы хотите, чтобы он был барьером переупорядочения памяти во время компиляции, для GNU C используйте asm ("nop" ::: "memory"); `.Я полагаю, что для MSVC это должно быть отдельным.