GCC的内联汇编程序识别声明符=r
和=&r
。这些对我来说很有意义:=r
让汇编器重用输入寄存器来输出。
但是,GCC的内联汇编程序还识别声明者+r
和+&r
。这对我来说不那么有意义。毕竟,+r
和+&r
之间的区别不是没有区别吗?仅仅+r
不足以告诉编译器保留一个寄存器以便单独使用单个变量吗?
例如,以下GCC代码有什么问题?
#include <stdio.h>
int main()
{
int a = 0;
printf("Initially, a == %d.\n", a);
/* The architecture is amd64/x86-64. */
asm(
"inc %[a]\n"
: [a] "+r" (a)
: : "cc"
);
printf("However, after incrementation, a == %d.\n", a);
return 0;
}
顺便提一下,我的内联汇编缺少输入声明,因为在我的(可能是错误的)思想中,+r
涵盖了输入,破坏,输出,一切。我有什么误会,拜托?
背景
我已经对汇编中的8位和16位微控制器进行了编程,但在托管环境中编码汇编方面几乎没有经验。
答案 0 :(得分:7)
默认情况下,GCC假定内联汇编语句由一个简单的单个汇编指令组成,该指令在写入任何输出操作数之前会消耗其所有输入操作数。当编写使用多个汇编指令的内联汇编语句时,这种假设经常被破坏,因此需要使用早期的clobber约束修饰符&
来指示在消耗所有输入操作数之前写入哪些输出操作数。对于使用=
修饰符的输出操作数和使用+
的读/写输出操作数,这都是必需的。例如,考虑以下两个函数:
int
foo() {
int a = 1;
asm("add %1, %0" : "+r" (a) : "r" (1));
return a;
}
int
bar() {
int a = 1;
asm("add %1, %0\n\t"
"add %1, %0"
: "+r" (a) : "r" (1));
return a;
}
两个内联汇编语句都使用相同的操作数和相同的约束,但foo
中唯一的内联汇编语句是正确的,bar
中的内联汇编语句被破坏。启用优化后,GCC会为这两个函数生成以下代码:
_foo:
movl $1, %eax
/APP
add %eax, %eax
/NO_APP
ret
_bar:
movl $1, %eax
/APP
add %eax, %eax
add %eax, %eax
/NO_APP
ret
GCC认为没有理由不在内联汇编语句中对两个操作数使用相同的寄存器EAX。虽然这不是foo
中的问题,但会导致bar
计算错误的结果4而不是预期的3。
bar
的正确版本将使用早期修补程序:
int
baz() {
int a = 1;
asm("add %1, %0\n\t"
"add %1, %0"
: "+&r" (a) : "r" (1));
return a;
}
_baz:
movl $1, %eax
movl %eax, %edx
/APP
add %edx, %eax
add %edx, %eax
/NO_APP
ret
当编译baz
时,GCC知道对两个操作数使用不同的寄存器,因此在第二次读取输入操作数之前修改读/写输出操作数并不重要。