C++ 中的扩展内联汇编:是否有必要保留易失性寄存器?

Extended Inline Assembly in C++: Is it necessary to preserve volatile registers?

我正在分析以下扩展内联汇编代码片段,returns 将作为参数给定的两个数字的总和传递给函数。

int addAB(int a, int b){
    int result;

    __asm__(
        "movl %%ebx, %%eax;"
        "addl %%ecx, %%eax;":
        "=a" (result) :
        "b" (b), "c" (a) :
        "cc"
    );


    return result;
}

根据我的理解,由于 EBX 是一个被调用者保存的寄存器,它的值意味着它需要在调用之间保留(是否有必要在这里进行 push-pop)?此外,指定了 CC 破坏器,这意味着条件代码已更改,但似乎条件代码几乎总是在更改(从将寄存器加在一起到比较寄存器)——条件代码何时可能不会更改?如果它们几乎总是可能发生变化,那么为每个 clobber 设置“cc”clobber 选项是否是一个安全的想法?

真的只有一条规则需要知道:

If you modify any(*) register, you must tell the compiler about it, either by declaring that register as an output operand, or by listing it as a clobber.

就是这样。寄存器是否“易失”(又名调用者保存)并不重要。如果你适当地通知编译器,那么你不需要自己保存和恢复寄存器,所以你的内联汇编中没有 push/pop 。如果需要,编译器会为您处理。 (实际上,在 x86-64 上,由于 ,你 必须 不能使用内联汇编将任何东西压入堆栈。)

(*) “Any”表示“编译代码可能使用的任何寄存器”;特别是所有通用整数、浮点数和向量寄存器。它不适用于编译器不会触及的系统或控制寄存器,也不适用于 x86 段寄存器,原因相同。如果您修改其中任何一项,您将对后果负责,并确保编译后的代码在新状态下仍能正常工作。 (例如,保留方向标志 DF 设置通常会导致编译代码失败,因为它希望它始终清晰。)

您的代码实际上并未修改 ebx。确实,编译器必须修改 ebx 才能将参数 b 加载到那里,但编译器很清楚 ebx 是非易失性的,因此它将负责保存和恢复它。 (事实上​​ ,当您的 asm 获得控制权时,无论如何保存它都为时已晚。)您的代码唯一修改的寄存器是 eax,并且它被声明为输出操作数,所以您很好。如果您确实修改了 ebx,则需要将其列为 "+b" 输入输出操作数(因为您需要将其作为输入)或作为破坏者(如果它还不是输入) - 但如果您修改了被调用者保存的 ecx.

,情况也是如此

请注意,如果此函数内联到更大的函数中,编译器可能已经想要 save/restore EBX 用于其他目的。或者这个函数可能会内联多次。您只想 save/restore 每个函数注册一次,而不是每个可能在循环中的 asm 语句一次,因此将其留给编译器是一个很好且明智的设计。

至于“cc”破坏者,我的意见是,当您的代码修改标志寄存器时,最好列出它,就像您的 add 指令所做的那样。但是,在 x86 上,由于有太多指令修改了标志,而且有太多程序员忘记将其列为破坏者,因此编译器假定每个内联 asm 都破坏了标志。因此,从技术上讲,您可以安全地省略 clobber。但是,这可能会养成坏习惯,因为在其他体系结构上,不会假定破坏,并且必须在适用时指定。参见 What happens in the assembly output when we add "cc" to clobber list