3
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”是干什么用的?您能否完整地解释所有代码?谢谢!

4

2 回答 2

3

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

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

输出

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

输入

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

集会

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


那么,为什么不使用d0andd1呢?我不知道。我也认为它们在这种情况下没用,整个输出部分可以留空,但我认为不可能在输入约束中指定“可写”和“早期破坏”。所以我认为d0d1在那里使成为&可能。

我会尝试这样写:

asm volatile (
    "rep\n"
    "stosb\n"
    :
    : "c" (n), "a" (c), "D" (s)
    : "%ecx", "%edi", "memory"
);
于 2012-03-20T13:53:22.453 回答
1

“d0”和“d1”是干什么用的?

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

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

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

这在技术上不是必需的%ecx,因为它被明确命名为输入:"0" (n)- 在这种情况下,'rep' 计数。我不确定两者是否有必要,因为在输入被消耗和指令执行%edi之前无法更新它。"1" (s)同样,由于它被明确命名为输入,编译器不能自由选择另一个寄存器。简而言之,“=&”在这里并没有什么坏处,但它什么也没做。

由于"a" (c)指定了一个仅输入寄存器%eax设置为(c),编译器可能会假设%eax在 'asm' 之后仍然保持这个值 - 确实是这种情况"rep; stosb;"

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

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

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


Reviewing some old answers, I thought I would add a link to the excellent Lockless GCC inline ASM tutorial. The article builds on prior sections, unlike the gcc manual which is best described as a 'reference', and not really suited to any sort of structured learning.

于 2012-03-20T15:58:38.827 回答