如何编写内联gnu扩展程序集的短块来交换两个整数变量的值?
为了娱乐,我正在使用带有32位Linux目标的x86的AT&T语法学习gnu扩展汇编。 我花了最后三个小时编写了两个可能的解决方案来解决我交换两个整数变量a
和b
的值的挑战,我的解决方案都没有完全解决我的问题。 首先,让我们更详细地看一下我的TODO障碍:
int main() { int a = 2, b = 1; printf("a is %d, b is %d\n", a, b); // TODO: swap a and b using extended assembly, and do not modify the program in any other way printf("a is %d, b is %d\n", a, b); }
在阅读本HOWTO之后 ,我编写了以下内联扩展汇编代码。 这是我第一次尝试交换整数:
asm volatile("movl %0, %%eax;" "movl %1, %%ecx;" "movl %%ecx, %0;" : "=r" (a) : "r" (b) : "%eax", "%ecx"); asm volatile("movl %%eax, %0;" : "=r" (b) : "r" (a) : "%eax", "%ecx");
我的理由是设置a = b,我需要一个扩展的程序集调用,它与程序集分开来设置b = a。 所以我编写了两个扩展的程序集调用,编译了我的代码,即gcc -m32 asmPractice.c,并运行了a.out。 结果如下:
a是2,b是1
a是1,b是1
看到它不能正常工作,我决定结合两个扩展的汇编程序调用,并写下:
asm volatile("movl %0, %%eax;" "movl %1, %%ecx;" "movl %%ecx, %0;" "movl %%eax, %1;" : "=r" (a) : "r" (b));
重新编译和链接后,我的代码仍然无法正确交换这两个值。 你自己看。 这是我的结果:
a是2,b是1
a是1,b是1
以下是评论中的一些解决方案:
解决方案#0(最佳选择): https : //gcc.gnu.org/wiki/DontUseInlineAsm
即使是零指令解决方案也会失败常量传播,以及任何其他涉及gcc知道任何值的优化。 它还强制编译器在该点同时在寄存器中同时具有两个变量。 在考虑使用inline-asm而不是builtins / intrinsics时,请始终牢记这些缺点。
解决方案#1: xchg
,其成本与大多数CPU上的3 mov
指令大致相同。
asm("xchg %0, %1;" : "+r" (a), "+r" (b));
解决方案#2:纯粹使用GNU C内联asm约束。
asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b));
查看Godbolt编译器资源管理器中的所有三个解决方案,包括它们击败优化的示例:
int swap_constraints(int a, int b) { asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b)); return a; } // Demonstrate the optimization-defeating behaviour: int swap_constraints_constants(void) { int a = 10, b = 20; return swap_constraints(a, b) + 15; } swap_constraints_constants: movl $10, %edx movl $20, %eax addl $15, %eax ret
与纯C交换相比:
swap_noasm_constants: movl $35, %eax # the add is done at compile-time, and `a` is optimized away as unused. ret