当您使用C ++中的asm代码操作它们时,寄存器会发生什么?

时间:2016-06-26 14:31:06

标签: c++ assembly inline-assembly cpu-registers

一些代码:

int x = 1;
for(int i = 1; i < 10; i++)
{
    x *= i;
    __asm {
        mov eax, x 
    };
}

如果此程序使用eax来增加i的值,那么当我操纵eax时会发生什么?

编译器是否会在__asm调用之前保存寄存器并在执行asm代码后使用它们,还是会忽略eax被操作并继续产生某种奇怪的行为?

内部eax会发生什么?

编辑:即使我的代码仅适用于Visual C ++,我想知道一般会发生什么以及不同的编译器将如何处理它。

3 个答案:

答案 0 :(得分:7)

  

内部eax会发生什么?

内联asm不是魔法或特殊的。 C ++编译器已经将C ++转换为asm。

你的内联asm只是包含在编译器生成的asm中。如果你想了解真正发生的事情,请查看编译器的asm输出,看看你的代码是如何适应的。

即。问题的这一部分的答案是它取决于编译器,上下文和优化选项,因此您应该只看生成的asm以便自己查看。

您的问题使用MSVC样式的内联asm,它保存/恢复内联asm周围的寄存器(ebp除外,当然还有esp)。所以我认为你的例子总是没有效果。 MSVC样式没有语法可以向编译器传达有关寄存器使用情况的任何信息,或者用于在寄存器中输入/输出值而不是内存。

您有时会看到MSVC inline asm在eax函数末尾的int中留下一个没有return语句的值,在有限的情况下做出了大多数安全假设编译器不会在inline-asm的结尾和函数的结尾之间对eax做任何事情。

你在评论中说,你想要一个g ++的答案,它甚至无法编译你的例子,但无论如何,我会为你写一个。

GNU C inline asm使用不同的语法,这要求您告诉编译器哪些寄存器是输入(而不是修改),哪些是读写或只写。还有哪些寄存器是破坏性的临时注册。

由程序员根据约束正确地描述asm到编译器,否则你将踩到脚趾。 使用GNU C inline asm就像一个舞蹈,你可以可能会取得好成绩,但只有你不小心你才会踩到编译器&#39;脚趾。 (另外,通常编译器可以自己制作好的asm,并且内联asm是一种非常脆弱的优化方法;许多主要问题之一是内联asm后内联不能继续传播。)

无论如何,让我们尝试一下GNU C inline asm的实例:

int foo_badasm(int n) {
  int factorial = 1;
  for (int i=1 ; i < n ; i++ ) {
    // compile with -masm=intel, since I'm using Intel syntax here
    asm volatile ("mov   eax, %[x]   # THIS LINE FROM INLINE ASM\n"
                  "# more lines\n"
                  // "xor  eax,eax\n"
        : // no outputs, making the volatile implicit even if we didn't specify it
        : [x] "rmi" (factorial)   // input can be reg, memory, or immediate
        : // "eax"   // uncomment this to tell the compiler we clobber eax, so our asm doesn't break step on the compiler's toes.
    );
    factorial *= i;
  }
  return factorial;
}

请参阅the Godbolt compiler explorer上带有asm输出的代码,以及没有asm语句的相同函数。

内部循环编译为此(gcc 6.1 -O3 -fverbose-asm -masm=intel-fno-tree-vectorize以保持简单)。你也可以用铿锵声来试试。

.L10:
    mov   eax, eax   # THIS LINE FROM INLINE ASM    # <retval>

    imul    eax, edx        # <retval>, i
    add     edx, 1    # i,
    cmp     edi, edx  # n, i
    jne     .L10      #,
    ret

正如您所看到的,在这种情况下,内联asm语句产生了一个no-op。 (mov eax,eaxrax截断为32位,将高32位归零,但在此函数中它们已经为零。)

如果我们不做任何其他事情,例如将寄存器归零,或者来自其他来源的mov,我们就会破坏该功能。编译器生成的asm仅依赖于 asm语句中列出的约束,而不依赖于代码文本(与MSVC不同)。

有关详情,请参阅代码Wiki,尤其是this answer on the difference between MSVC and GNU C inline asm

更重要的是,在实际使用内联asm之前阅读https://gcc.gnu.org/wiki/DontUseInlineAsm

答案 1 :(得分:3)

简短的回答:“这是你的脚...不要拍它!”

如果您使用asm{}块,C / C ++希望您知道自己在做什么。它不会生成任何代码来“准备”您的插入,也不会“清理它”,不会知道或考虑您的操作。在这种情况下,你会在脚下射击自己。您刚刚通过执行编译器没有预料到的,不知道的事情并且可能会干扰编译器生成的代码 所做的事情 - 来介绍一个您自己制作的错误。

如果您打算在汇编代码中操作寄存器,则必须采取所有适当的步骤来保留和恢复这些寄存器的状态。你必须知道完全你正在做什么。

答案 2 :(得分:1)

恕我直言,最好的方法是不使用内联ASM,而是在ASM中编写单独的函数。

通常的步骤是:

  1. 用C或C ++编写代码,使其正常运行
  2. 打印出编译器生成的汇编列表。
  3. 使用编译器生成的汇编代码作为基础 (这使得编写调用和返回代码更容易)。
  4. 请注意,编写汇编语言以优化编译器生成的代码通常会浪费开发时间。您应该在优化之前进行分析。

    此外,编写内联汇编不可移植。