我正在尝试使用内联汇编的示例:http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html 但是有些事让我感到困惑:
“好吧,当GCC能够确切地知道你在前后对寄存器做了什么时,它确实很有帮助....它甚至足够聪明地知道如果你告诉它放(x + 1)在寄存器中,如果你没有破坏它,后来C代码引用(x + 1),并且它能够保持该寄存器空闲,它将重用计算。哇。“
教程中有关clobber列表的一些不一致:
对于输入/输出列表中指定的寄存器,不需要像GCC知道的那样将它们放入clobber列表中;但是在关于rep_movsl(或rep_stosl)的示例中:
asm(“cld \ n \ t” “REP \ n \ t” 的 “stosl” :/ *没有输出寄存器* / :“c”(计数),“a”(fill_value),“D”(dest) :“%ecx”,“%edi”);
虽然“S,D,c”在输出操作数中,但它们再次被列为破坏。 我在C中尝试了一个简单的片段:
#include<stdio.h>
int main()
{
int a[] = {2, 4, 6};
int b[3];
int n = 3;
int v = 12;
asm ("cld\n\t"
"rep\n\t"
"movsl"
:
: "S" (a), "D" (b), "c" (n)
: );
// : "%ecx", "%esi", "%edi" );
printf("%d\n", b[1]);
}
如果我使用评论的clobber列表,GCC会抱怨:
a.c:8:3:错误:重新加载时无法在“CREG”类中找到寄存器 'asm'a.c:8:3:错误:'asm'操作数有不可能的约束
如果我使用空的clobber列表,它将编译并输出为4。
答案 0 :(得分:6)
您引用的文档似乎非常不准确。以下是asm操作数约束对GCC的实际意义:
此外,目前(GCC 4.7)手册包括这一关键段落:
您不能以与输入或输出操作数重叠的方式编写clobber描述。例如,如果在clobber列表中提及该寄存器,则可能没有描述具有一个成员的寄存器类的操作数。声明存在于特定寄存器中的变量(请参阅显式寄存器变量),并用作asm输入或输出操作数必须没有在clobber描述中提及的部分。您无法指定修改输入操作数而不将其指定为输出操作数。请注意,如果您指定的所有输出操作数都是为此目的(因此未使用),那么您还需要为asm构造指定volatile,如下所述,以防止GCC将asm语句删除为未使用。
这就是为什么尝试同时输入和破坏某些寄存器失败的原因。
现在,插入rep movsl现在有点愚蠢 - 只需使用memcpy
并让GCC用最佳指令序列替换它 - 但是编写示例的正确方法是
int main()
{
int a[] = {2, 4, 6};
int b[3];
int n = 3;
int v = 12;
int *ap = a, *bp = b;
asm volatile ("rep movsl" : "+S" (ap), "+D" (bp), "+c" (n) : : "memory");
printf("%d\n", b[1]);
}
您需要ap
和bp
个中间变量,因为数组的地址不是左值,因此它不会出现在输出约束中。 “+ r”符号告诉GCC该寄存器既是输入又是输出。 'volatile'是必要的,因为所有的输出操作数在asm
之后都没有被使用,所以GCC否则会高兴地删除它(理论上它只是它对输出操作数的作用)。将“内存”放在clobber列表中就是告诉GCC操作修改内存的方法。最后,微观优化:GCC永远不会发出'std',所以你不需要'cld'(这实际上是由x86 ABI保证的。)
我所做的大多数更改都不会影响像这样的小型测试程序是否正常运行;但是,它们在全尺寸程序中都是必不可少的,以防止出现细微的优化错误。例如,如果你遗漏了“记忆”咒语,那么海湾合作委员会将有权将b[1]
的{{1}}加载到asm
之上!