如何编写一小段内联 gnu 扩展程序集来交换两个整数变量的值?
How to write a short block of inline gnu extended assembly to swap the values of two integer variables?
为了娱乐,我正在学习 gnu 扩展汇编,使用 AT&T 语法为 x86 和 32 位 Linux 目标。我刚刚花了最后三个小时编写了两个可能的解决方案来应对交换两个整数变量 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);
}
看完this 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 is 2, b is 1
a is 1, b is 1
看到它无法正常工作,然后我决定将两个扩展的汇编程序调用结合起来,并写下:
asm volatile("movl %0, %%eax;"
"movl %1, %%ecx;"
"movl %%ecx, %0;"
"movl %%eax, %1;"
: "=r" (a)
: "r" (b));
重新编译和链接后,我的代码仍然没有正确交换两个值。你自己看。这是我的结果:
a is 2, b is 1
a is 1, b is 1
以下是评论中的一些解决方案:
解决方案#0(最佳选择):https://gcc.gnu.org/wiki/DontUseInlineAsm
即使是零指令解决方案也能击败常量传播,以及任何其他涉及 gcc 知道任何关于该值的优化。它还会强制编译器在此时同时将两个变量保存在寄存器中。在考虑使用内联汇编而不是内置函数/内在函数时,请始终牢记这些缺点。
解决方案 #1:x86 xchg
,没有暂存器,并且可以在 AT&T 和 Intel 语法模式下工作。 在大多数 Intel CPU 上,或在某些 AMD 上只有 2 微指令。
asm("xchg %0, %1;" : "+r" (a), "+r" (b));
解决方案 #2:纯粹使用 GNU C 内联 asm 约束。(奖励:可移植到所有架构)
asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b));
查看所有三个解决方案的实际应用 on the Godbolt compiler explorer,包括它们击败优化的示例:
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 , %edx
movl , %eax
addl , %eax
ret
对比使用纯 C 交换:
swap_noasm_constants:
movl , %eax # the add is done at compile-time, and `a` is optimized away as unused.
ret
为了娱乐,我正在学习 gnu 扩展汇编,使用 AT&T 语法为 x86 和 32 位 Linux 目标。我刚刚花了最后三个小时编写了两个可能的解决方案来应对交换两个整数变量 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);
}
看完this 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 is 2, b is 1
a is 1, b is 1
看到它无法正常工作,然后我决定将两个扩展的汇编程序调用结合起来,并写下:
asm volatile("movl %0, %%eax;"
"movl %1, %%ecx;"
"movl %%ecx, %0;"
"movl %%eax, %1;"
: "=r" (a)
: "r" (b));
重新编译和链接后,我的代码仍然没有正确交换两个值。你自己看。这是我的结果:
a is 2, b is 1
a is 1, b is 1
以下是评论中的一些解决方案:
解决方案#0(最佳选择):https://gcc.gnu.org/wiki/DontUseInlineAsm
即使是零指令解决方案也能击败常量传播,以及任何其他涉及 gcc 知道任何关于该值的优化。它还会强制编译器在此时同时将两个变量保存在寄存器中。在考虑使用内联汇编而不是内置函数/内在函数时,请始终牢记这些缺点。
解决方案 #1:x86 xchg
,没有暂存器,并且可以在 AT&T 和 Intel 语法模式下工作。
asm("xchg %0, %1;" : "+r" (a), "+r" (b));
解决方案 #2:纯粹使用 GNU C 内联 asm 约束。(奖励:可移植到所有架构)
asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b));
查看所有三个解决方案的实际应用 on the Godbolt compiler explorer,包括它们击败优化的示例:
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 , %edx
movl , %eax
addl , %eax
ret
对比使用纯 C 交换:
swap_noasm_constants:
movl , %eax # the add is done at compile-time, and `a` is optimized away as unused.
ret