我很难理解角色约束在GCC内联汇编(x86)中的作用。我read the manual,它准确地解释了每个约束的作用。问题在于,即使我理解每个约束的作用,我也很少理解为什么要使用一个约束而不是另一个约束,或者它可能带来什么。
我意识到这是一个非常广泛的话题,所以一个小例子应该有助于缩小焦点。以下是一个简单的asm例程,它只添加了两个数字。如果发生整数溢出,则会将值1
写入输出C变量。
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
中。我最终读到了“matching constraints”,它允许您指定变量将用作输入和输出操作数。当我将"m"(b)
更改为"0"(b)
时,它就有效了。
但我真的不明白为什么你会使用一个约束而不是另一个约束。我的意思是的,我明白“r”意味着变量应该在寄存器中,“m”意味着它应该在内存中 - 但我真的 另一个是,或者如果我选择某种约束组合,加法操作无法正常工作的原因。
问题:1)在上面的示例代码中,为什么b
上的“m”约束导致c
被写入? 2)是否有任何教程或在线资源更详细地介绍约束?
答案 0 :(得分:13)
这里有一个例子可以更好地说明为什么你应该仔细选择约束(与你的功能相同,但也许更简洁一点):
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;
}
因此,使用的约束是:q
,r
和g
。
q
表示只能选择eax
,ecx
,edx
或ebx
。这是因为set*
指令必须写入8位可寻址寄存器(al
,ah
,...)。在b
中使用%b0
表示使用最低的8位部分(al
,cl
,...)。m
或g
;至少对其中一个操作数使用r
。g
(一般)。在上面的示例中,我选择对g
使用r
(而不是a
),因为引用通常实现为内存指针,因此使用r
约束本来需要先将指示对象复制到寄存器,然后再复制。使用g
,可以直接更新指示对象。
至于为什么你的原始版本用加法值覆盖你的c
,那是因为你在输出槽中指定了=m
,而不是(比如说)+m
;这意味着允许编译器为输入和输出重用相同的内存位置。
在您的情况下,这意味着两个结果(因为b
和c
使用了相同的内存位置):
c
被b
的值覆盖(添加的结果)。c
变为1(并且b
也可能变为1,具体取决于代码的生成方式。)