我知道何时使用补鞋匠列表(例如列出在程序集中修改的寄存器,以便它不被选择用作输入寄存器等),但我无法绕过早期约束&
。如果列出输出,那是否已经意味着输入不能使用所选寄存器(除了匹配数字约束)?
例如:
asm(
"movl $1, %0;"
"addl $3, %0;"
"addl $4, %1;"
"addl %1, %0;"
: "=g"(num_out)
: "g"(num_in)
:
);
输出变量甚至需要&
吗?编译器应该知道为输出选择的寄存器,因此知道不要将它用作输入。
答案 0 :(得分:14)
默认情况下,编译器假定在写入任何输出寄存器之前将消耗所有输入,因此允许它们使用相同的寄存器。这样可以在可能的情况下获得更好的代码,但如果假设是错误的,事情将会灾难性地失败。 “早期clobber”标记是一种告诉编译器在输出所有输入之前将写入此输出的方法,因此它不能与任何输入共享寄存器。
答案 1 :(得分:4)
基本教育示例
在这里,我提供了一个简单的教育示例,试图使https://stackoverflow.com/a/15819941/895245所提到的内容更加清楚。
此代码在实践中当然没有用,可以通过一条lea 1(%q[in]), %out
指令来更有效地实现。
main.c
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in = 1;
uint64_t out;
__asm__ (
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
: [out] "=&r" (out)
: [in] "r" (in)
:
);
assert(out == 2);
}
编译并运行:
gcc -ggdb3 -std=c99 -O3 -Wall -Wextra -pedantic -o main.out main.c
./main.out
该程序是正确的,并且断言通过了,因为&
强制编译器为in
和out
选择不同的寄存器。
这是因为&
告诉编译器在写入in
之后可能会使用out
,实际上就是这种情况。
因此,不错误地修改in
的唯一方法是将in
和out
放在不同的寄存器中。
反汇编:
gdb -nh -batch -ex 'disassemble/rs main' main.out
包含:
0x0000000000001055 <+5>: 48 89 d0 mov %rdx,%rax
0x0000000000001058 <+8>: 48 ff c0 inc %rax
0x000000000000105b <+11>: 48 89 d0 mov %rdx,%rax
0x000000000000105e <+14>: 48 ff c0 inc %rax
这表明GCC为rax
选择了out
,为rdx
选择了in
。
但是,如果我们删除&
,则行为未指定。
在我的测试系统中,断言实际上失败了,因为编译器试图使寄存器的使用最小化,并编译为:
0x0000000000001055 <+5>: 48 89 c0 mov %rax,%rax
0x0000000000001058 <+8>: 48 ff c0 inc %rax
0x000000000000105b <+11>: 48 89 c0 mov %rax,%rax
0x000000000000105e <+14>: 48 ff c0 inc %rax
因此,rax
和in
都使用out
。
其结果是out
增加了两次,并且最后等于3
而不是2
。
在Ubuntu 18.10 amd64,GCC 8.2.0中进行了测试。
更多实用示例