有一种在C / C ++中生成nop操作码的可移植方法吗?

时间:2018-01-14 01:59:42

标签: c++ c

在汇编中,有nop操作码。让我们说我想单独使用C / C ++语法在生成的程序集中使用该操作码。有没有可行的方法来实现它?

在阅读this之后,我尝试在最新的MSVC 15.5.2中编译(void)0;,但没有成功生成nop指令,无论是否启用了优化。

由于ISO C标准的内联汇编is not及其在C ++中的支持是conditional,我想避免这种情况。

在Microsoft Visual Studio中,我知道我可以使用标题为here的标头<intrin.h>中定义的__nop()函数在生成的程序集中成功创建单个nop。 / p>

3 个答案:

答案 0 :(得分:5)

不,没有可移植的方式。

虽然编译器通常会为或多或少的异常情况提供相当多的内在函数,并且通常也有一些内联编写汇编代码的方法,但这些都不是标准化的。

任何明显对应于“无操作”的未受保护的源级构造都不会有机会对抗最基本的优化。

答案 1 :(得分:1)

否且没有意义在映射到程序集nop的高级别上执行nop操作。为什么?由于as if rule表明编译器“需要模拟(仅)可观察行为”。根据定义,nop操作没有任何类型的可观察行为(在标准中定义的“抽象机器”上)。

对于更实际的方面,从C ++源文件生成的汇编指令与C ++指令没有1对1的关系。大多数情况下,C ++指令由多个汇编指令组成,多个C ​​++指令由一个汇编指令替换,甚至一些C ++指令也没有任何类型的汇编计数器部分到期算法转换或死代码消除,并且在该指令之上重新排列,算法在各处都得到了改变。

例如

我能想到编译器可以做的算法转换的最激进的例子之一是将递归函数转换为简单的迭代循环:

auto sum(int* v, int len)
{
    if (len == 0)
        return 0;

    return v[0] + sum(v + 1, len - 1);
}

一个简单的递归函数,用于计算向量元素的总和。

这是clang生成的-O1

sum(int*, int): # @sum(int*, int)
  xor eax, eax
  test esi, esi
  je .LBB0_2
.LBB0_1: # =>This Inner Loop Header: Depth=1
  add eax, dword ptr [rdi]
  add rdi, 4
  add esi, -1
  jne .LBB0_1
.LBB0_2:
  ret

从递归到循环的巧妙算法转换。 nop C ++指令在哪里适合程序集?

-O3生成的程序集中如何?:

sum(int*, int): # @sum(int*, int)
  test esi, esi
  je .LBB0_1
  lea edx, [rsi - 1]
  add rdx, 1
  xor eax, eax
  cmp rdx, 8
  jae .LBB0_4
  mov rcx, rdi
  jmp .LBB0_7
.LBB0_1:
  xor eax, eax
  ret
.LBB0_4:
  mov r8d, esi
  and r8d, 7
  sub rdx, r8
  sub esi, edx
  lea rcx, [rdi + 4*rdx]
  add rdi, 16
  pxor xmm0, xmm0
  pxor xmm1, xmm1
.LBB0_5: # =>This Inner Loop Header: Depth=1
  movdqu xmm2, xmmword ptr [rdi - 16]
  paddd xmm0, xmm2
  movdqu xmm2, xmmword ptr [rdi]
  paddd xmm1, xmm2
  add rdi, 32
  add rdx, -8
  jne .LBB0_5
  paddd xmm1, xmm0
  pshufd xmm0, xmm1, 78 # xmm0 = xmm1[2,3,0,1]
  paddd xmm0, xmm1
  pshufd xmm1, xmm0, 229 # xmm1 = xmm0[1,1,2,3]
  paddd xmm1, xmm0
  movd eax, xmm1
  test r8d, r8d
  je .LBB0_8
.LBB0_7: # =>This Inner Loop Header: Depth=1
  add eax, dword ptr [rcx]
  add rcx, 4
  add esi, -1
  jne .LBB0_7
.LBB0_8:
  ret

这里编译器会进行一些循环展开。循环现在转换为一个标题,它将所有元素转换为多个元素,一个主体,使用矢量化同时添加对齐的元素组,以及一个尾部,它可以获得无法填充对齐组的其余元素。并且...有趣的事情......原始的C ++源代码甚至没有循环。所以如果你在源代码中有一个nop,你会把它放在程序集中吗?它没有可观察到的影响,所以没有理由让编译器可以利用它来确定将它放在这个重度转换的代码中的位置。

即使你想到了一些聪明的规则并设法指定C ++ nop映射到汇编的位置,它会有多大用处?程序集nop的目的与程序算法无关,但与架构实现细节有关,如RAW依赖项等。使用C ++,您不需要对架构详细信息(至少不是标准C ++)进行建模,而是对架构无关的算法进行建模,因此您无法获得与架构详细信息完全相关的指令。

问题在于C ++确实有nop指令。它是空语句;;。它可以在C ++语法和语义级别上有用:

// find the end of the string:

for (const ch* end = str; *ch != '\0'; ++end)
    ; // <-- empty statement. A nop instruction.

但是由于上述规则和共鸣,生成汇编nop指令没有意义。

答案 2 :(得分:0)

不,大多数现代编译器会自动浏览您的代码并重新排列,以便针对您选择的架构进行优化。在重新安排您的时候,编译器会消除任何没有后续依赖关系的代码(在Visual Studio中,您经常可以观察到IDE下划线的变量,这些变量在以后的时间点没有使用;这些变量在编译期间被删除)。如果你想维护一个nop,你将不得不坚持使用特定的编译器。