内联汇编中匹配约束有什么用

What is the use of matching constraints in inline assembly

来自以下link, https://www.ibm.com/developerworks/library/l-ia/index.html

a single variable may serve as both the input and the output operand.

我写了下面的代码:

#include <stdio.h>

int main()
{   
    int num = 1;
    asm volatile ("incl %0"
            :"=a"(num)
            :"0"(num));
    printf("num:%d\n", num);
    return 0;
}

以上代码递增 num 的值。

匹配约束有什么用,如果我不使用匹配约束,代码不会按预期运行。

asm volatile ("incl %0"
                :"=a"(num));

此代码:

asm volatile ("incl %0"
                :"=a"(num));

不起作用,因为为了增加寄存器中的值(在本例中为 1),需要从寄存器中读取原始值; 1 添加到它;并将值写回寄存器。 =a 只是说寄存器 EAX 的输出在完成后将被移动到 num,但编译器不会将 num 的原始值加载到寄存器 EAX。上面的代码只会将 1 添加到 EAX 中的任何内容(可以是任何内容)并在内联汇编完成时将其放入 num

asm volatile ("incl %0"
        :"=a"(num)
        :"0"(num));

另一方面,这表示 num 既被用作输入(因此 num 的值被移动到 EAX)并且它还在 EAX 中输出一个值,因此编译器当内联汇编完成时,会将 EAX 中的值移动到 num

它也可以被重写为使用 input/output 约束(这做同样的事情):

asm volatile ("incl %0"
        :"+a"(num));

这里也不需要 volatile,因为所有的副作用都包含在约束中。不必要地添加 volatile 会导致代码生成效率降低,但代码仍然有效。我会这样写:

asm ("incl %0"
     :"+a"(num));

why and when should we use matching constraints

这不是你问的问题;你问为什么你需要一个输入,当你知道语法的实际含义时,这应该是相当明显的。 ("=r"(var) 是一个纯输出,独立于 C 变量之前的任何值,就像 var = 123; 一样)。所以 "=r"inc 指令就像 var = stale_garbage + 1;


但是无论如何,正如我评论的那样,有趣的问题是“当您可以将 "+r"(var) 用于 read/write 操作数而不是更多时,为什么存在匹配约束复杂的匹配约束语法?"

它们很少有用;通常你可以对输入和输出使用相同的变量,特别是如果你的 asm 位于 C 包装函数 中。但是,如果您不想对输入和输出使用相同的 C var,但仍需要它们选择相同的寄存器或内存,那么您需要一个匹配约束。一个用例可能是包装系统调用是一个用例;您可能希望对索书号使用不同的 C 变量而不是 return 值。 (除了你可以只使用 "=a""a" 而不是匹配约束;编译器没有选择。)或者输出变量可能比输入变量更窄或类型不同另一个用例。

IIRC,x87 是另一个用例;我好像记得 "+t" 不工作。

我认为 "+r" RMW 约束在内部实现为具有 "hidden" 匹配约束的输出。但是,虽然 %1 通常在只有一个操作数的 asm 模板中出错,但如果该操作数是 in/out "+something" 那么 GCC 不会拒绝 %1 因为它太高了操作数。如果您查看 asm 以查看它实际为该越界操作数选择了哪个寄存器或内存,它确实与 in/out 操作数匹配。

所以"+r"基本上是匹配约束的语法糖。我不确定它在某个时候是否是新的,在 GCC 版本 x.y 之前你必须使用匹配约束?对于输入和输出使用具有相同 var 的匹配约束的教程示例并不少见,这些示例使用 "+" RMW 约束更易于阅读。


基础知识:

使用 "a""=a" 等约束,您 不需要 匹配约束;无论如何,编译器只有一种选择。它有用的地方是 "=r",编译器可以在其中选择任何寄存器,而您需要它为输入操作数选择 相同的 寄存器。

如果您只使用 "=r" 和一个单独的 "r" 输入,您会告诉编译器它可以将其用作复制和任何操作,而不会修改原始输入并在新的寄存器中产生输出。或者,如果需要,可以覆盖输入。这适合 lea 1(%[srcreg]), %[dstreg] 但不适合 inc %0。后者会假定 %0 和 %1 是同一个寄存器,因此您需要做一些事情来确保这是真的!