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。
我正在分析以下扩展内联汇编代码片段,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。