GCC内联汇编:约束条件

23
我很难理解GCC内联汇编(x86)中约束的作用。 我已经读过手册,它详细解释了每个约束所做的事情。 问题是,即使我了解每个约束的作用,我对为什么会使用一个约束而不是另一个约束或其影响有很少的了解。
我意识到这是一个非常广泛的话题,因此一个小例子应该有助于缩小重点。 以下是一个简单的汇编程序,只需将两个数字相加。 如果发生整数溢出,则向输出C变量写入值1
 int32_t a = 10, b = 5;
 int32_t c = 0; // overflow flag

 __asm__
 (
  "addl %2,%3;"        // Do a + b (the result goes into b)
  "jno 0f;"            // Jump ahead if an overflow occurred
  "movl $1, %1;"       // Copy 1 into c
  "0:"                 // We're done.

  :"=r"(b), "=m"(c)    // Output list
  :"r"(a), "0"(b)     // Input list
 );

现在这个方案可以正常工作,但是我必须任意地调整约束条件才能使其正确运行。最初,我使用了以下约束条件:

  :"=r"(b), "=m"(c)    // Output list
  :"r"(a), "m"(b)     // Input list

请注意,我在b上使用了一个"m"约束条件,而不是"0"。这导致了一个奇怪的副作用,即如果我使用优化标志编译并调用函数两次,某种原因下加法操作的结果也会被存储在c中。我最终读到了关于"匹配约束条件"的内容,它允许您指定变量既用作输入操作数又用作输出操作数。当我将"m"(b)改为"0"(b)时它就可以工作了。
但我真的不明白为什么你会使用一个约束条件而不是另一个。我的意思是,是的,我知道"r"表示变量应该在寄存器中,而"m"表示它应该在内存中 - 但我不太理解选择一个约束条件与选择另一个约束条件的影响,或者为什么如果我选择某些约束条件,加法操作就不能正常工作。
问题:1)在上面的示例代码中,为什么b上的"m"约束条件会导致写入c?2)是否有任何教程或在线资源详细介绍约束条件?
1个回答

19

这里有一个例子来更好地说明为什么你应该仔细选择约束(与您的功能相同,但可能写得更加简洁):

bool add_and_check_overflow(int32_t& a, int32_t b)
{
    bool result;
    __asm__("addl %2, %1; seto %b0"
            : "=q" (result), "+g" (a)
            : "r" (b));
    return result;
}
所以使用的约束条件是:qrg
  • q 表示只能选择 eaxecxedxebx。这是因为 set* 指令必须写入一个可寻址 8 位寄存器 (al, ah, ...)。在 %b0 中使用 b 的意思是,使用最低 8 位部分 (al, cl, ...)。
  • 对于大多数双操作数指令,至少有一个操作数必须是寄存器。因此,不要同时使用 mg 作为两个操作数的约束条件;至少将一个操作数使用 r
  • 对于最后一个操作数,它是寄存器还是内存都无所谓,所以使用 g(通用)约束条件。

在上面的示例中,我选择使用 g(而不是 r)来约束 a,因为引用通常被实现为内存指针,因此使用 r 约束条件需要先将引用对象复制到一个寄存器中,然后再将其复制回来。使用 g 约束条件可以直接更新引用对象。


至于为什么原始版本会用加法结果覆盖 c,那是因为你在输出位置指定了 =m,而不是(比如)+m;这意味着编译器可以重用相同的内存位置作为输入和输出。

在你的情况下,这意味着有两种可能的结果(由于相同的内存位置用于 bc):

  • 加法未溢出:此时,c 被覆盖为 b 的值(即加法的结果)。
  • 加法溢出了:此时,c 变为 1(并且 b 可能也会变为 1,具体取决于代码生成的方式)。

谢谢 - 这是一个很好的答案。只有一个澄清:为什么“=(只写)约束修饰符会给编译器重新使用相同的内存位置的权利,即使bc`是具有不同内存位置的不同变量? - Channel72
@Channel72:“即使bc是不同的变量,具有不同的内存位置”——这实际上是一个重要的假设,经常不适用。如果bc是局部变量,很有可能它们都由寄存器支持,而不是内存位置。在这种情况下,内存位置只是一个临时的存储位置,纯粹是为了容纳您的m约束而设置的,此时bc可能会使用相同的临时位置。 - C. K. Young
现在,如果bc实际上都是由内存位置支持的,那么你就是正确的,通常它们不应该重叠。而且,如果一个由内存支持,另一个由寄存器支持...那么这两种情况都有可能。 - C. K. Young

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接