“+&r”与“+r”有何不同?

How does "+&r" differ from "+r"?

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 位微控制器进行了一些编程,但在托管环境中编码汇编的经验很少或没有。

GCC 默认假定内联汇编语句由一个简单的单个汇编指令组成,该指令在写入其任何输出操作数之前消耗其所有输入操作数。当编写使用多个汇编指令的内联汇编语句时,这种假设经常被打破,因此需要使用早期破坏约束修饰符 & 来指示在消耗所有输入操作数之前写入哪些输出操作数。这对于使用 = 修饰符的输出操作数和使用 + 的 read/write 输出操作数都是必需的。例如考虑以下两个函数:

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    , %eax
/APP
    add %eax, %eax
/NO_APP
    ret

_bar:
    movl    , %eax
/APP
    add %eax, %eax
    add %eax, %eax
/NO_APP
    ret

GCC 认为没有理由不对两个内联汇编语句中的两个操作数使用相同的寄存器 EAX。虽然这在 foo 中不是问题,但它会导致 bar 计算错误结果 4 而不是预期的 3。

bar 的正确版本将使用早期的 clobber 修饰符:

int
baz() {
    int a = 1;
    asm("add %1, %0\n\t"
        "add %1, %0"
        : "+&r" (a) : "r" (1));
    return a;
}
_baz:
    movl    , %eax
    movl    %eax, %edx
/APP
    add %edx, %eax
    add %edx, %eax
/NO_APP
    ret

编译时 baz GCC 知道为两个操作数使用不同的寄存器,因此在第二次读取输入操作数之前修改 read/write 输出操作数并不重要。