“+& r”与“+ r”有什么不同?

时间:2017-08-26 12:50:51

标签: c gcc x86 inline-assembly

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位微控制器进行了编程,但在托管环境中编码汇编方面几乎没有经验。

1 个答案:

答案 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知道对两个操作数使用不同的寄存器,因此在第二次读取输入操作数之前修改读/写输出操作数并不重要。