Oxd*_*eef 1 c x86 assembly gcc inline-assembly
我正在尝试使用内联汇编的示例:http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html 但是有些事让我感到困惑:
"好吧,当GCC能够确切地知道你之前和之后对寄存器做了什么时,这确实很有帮助......如果你告诉它将(x + 1)放入寄存器中,它甚至足够聪明. ,如果你没有破坏它,后来C代码引用(x + 1),并且它能够保持该寄存器空闲,它将重用计算.哇."
关于clobber列表的教程中存在一些不一致:
对于输入/输出列表中指定的寄存器,不需要将它们放入clobber列表中,因为GCC知道; 但是在关于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]);
}
Run Code Online (Sandbox Code Playgroud)
如果我使用评论的clobber列表,GCC会抱怨:
ac:8:3:错误:在重新加载'asm'ac时,无法在类'CREG'中找到寄存器:8:3:错误:'asm'操作数有不可能的约束
如果我使用空的clobber列表,它将编译并输出为4.
您引用的文档似乎非常不准确.以下是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]);
}
Run Code Online (Sandbox Code Playgroud)
您需要ap和bp中间变量,因为数组的地址不是左值,因此它不能出现在输出约束中."+ r"符号告诉GCC该寄存器既是输入又是输出.'volatile'是必要的,因为所有的输出操作数在之后都没有被使用asm,所以GCC否则会高兴地删除它(理论上它只是它对输出操作数的作用).将"内存"放在clobber列表中就是告诉GCC操作修改内存的方法.最后,一个微优化:GCC永远不会发出'std',所以你不需要'cld'(这实际上是由x86 ABI保证的).
我所做的大多数更改都不会影响像这样的微小测试程序是否正常运行; 但是,它们在全尺寸程序中都是必不可少的,以防止出现细微的优化错误.例如,如果你遗漏了"记忆"的破坏,GCC将有权提升b[1]上面的负荷asm!