内联asm未知

时间:2012-03-20 13:36:44

标签: c gcc assembly inline-assembly att

static inline void *__memset(void *s, char c, size_t n) {
int d0, d1;
asm volatile (
    "rep; stosb;"
    : "=&c" (d0), "=&D" (d1)
    : "0" (n), "a" (c), "1" (s)
    : "memory");
return s;
}

“d0”和“d1”用于什么?能否完全解释一下所有代码?谢谢!

2 个答案:

答案 0 :(得分:4)

您需要了解gcc扩展内联asm格式:

  • 第一部分是实际装配。在这种情况下,只有2条指令
  • 第二部分指定输出约束,第三部分指定输入约束。第四部分指定程序集将破坏内存

输出

  • "=&c"将d0与ecx寄存器相关联,并将其标记为只写。 &表示可以在代码结束之前修改
  • 对于edi寄存器,
  • "=&D"的含义相同

输入

  • "0" (n)将n与第一个提到的注册表联系起来。在您的情况下,使用ecx
  • "a" (c)将c与eax联系起来
  • "1" (s)与edi
  • 联系

装配

所以你有它。重复这个ecx次(n次):将eax(c)存储到edi(s)然后递增它。


那么,为什么未使用的d0d1 ?我不确定。我也认为在这种情况下它们没用,并且整个输出部分可以留空但我认为不可能在输入约束中指定“可写”和“早期 -clobbered”。所以我认为d0d1可以使&成为可能。

我会尝试这样写:

asm volatile (
    "rep\n"
    "stosb\n"
    :
    : "c" (n), "a" (c), "D" (s)
    : "%ecx", "%edi", "memory"
);

答案 1 :(得分:0)

什么是“d0”和“d1”用于?

实际上,它表示%ecx%edi(假设为32位)的最终值分别存储在d0d1中。这有几个目的:

它让编译器知道,作为输出,这些寄存器被有效地破坏了。通过将它们分配给临时变量,优化编译器也知道不需要实际执行“存储”操作。

“=&”将这些指定为 early-clobber 操作数。可以在消耗所有输入之前写入它们。因此,如果编译器可以自由选择输入寄存器,则它不应该将这两个别名。

这对于%ecx来说在技术上并不是必需的,因为它明确地命名为输入:"0" (n) - 在这种情况下是'rep'计数。我不确定%edi是否有必要,因为在输入"1" (s)之前无法更新,并且执行了指令。再次,因为它的显式被命名为输入,编译器不能自由选择另一个寄存器。简而言之,“=&”这里没有受伤,但它没有做任何事情。

由于"a" (c)指定仅输入寄存器%eax设置为(c),编译器可能会认为%eax仍然保留此值'asm' - "rep; stosb;"确实如此。

"memory"指定可以以编译器未知的方式修改内存 - 在这种情况下是正确的,它将(n)字节从(r)开始设置为值{{1假设方向标志被清除,它应该是。这确实具有强制重载值的效果,因为编译器不能假设寄存器反映了它们应该再次存储的值。它没有伤害,可能有必要使一般情况(c)安全,但它往往是矫枉过正。

编辑:输入操作数可能与clobber操作数不重叠。将某些内容指定为仅输入 clobbered 是没有意义的。我不认为编译器允许这样做,即使它使用了模糊规范也是不明智的。从手册:

  

您不能以与输入或输出操作数重叠的方式编写clobber描述。例如,如果您在clobber列表中提到该寄存器,则可能没有描述具有一个成员的寄存器类的操作数。