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”是干什么用的?您能否完整地解释所有代码?谢谢!
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”是干什么用的?您能否完整地解释所有代码?谢谢!
您需要了解 gcc 扩展的内联 asm 格式:
"=&c"
将 d0 与 ecx 寄存器相关联并将其标记为只写。&
表示可以在代码结束前修改"=&D"
意思是一样的,对于 edi 寄存器"0" (n)
将 n 与第一个提到的寄存器相关联。在您的情况下,使用 ecx"a" (c)
将 c 与 eax 关联"1" (s)
将 s 与 edi 联系起来所以你有它。重复此 ecx 次(n 次):将 eax (c) 存储到 edi (s) 中,然后将其递增。
那么,为什么不使用d0
andd1
呢?我不知道。我也认为它们在这种情况下没用,整个输出部分可以留空,但我认为不可能在输入约束中指定“可写”和“早期破坏”。所以我认为d0
并d1
在那里使成为&
可能。
我会尝试这样写:
asm volatile (
"rep\n"
"stosb\n"
:
: "c" (n), "a" (c), "D" (s)
: "%ecx", "%edi", "memory"
);
“d0”和“d1”是干什么用的?
实际上,它表示 的最终值%ecx
(%edi
假设为 32 位)分别存储在d0
中d1
。这有两个目的:
它让编译器知道,作为输出,这些寄存器被有效地破坏了。通过将它们分配给临时变量,优化编译器也知道不需要实际执行“存储”操作。
“=&”将这些指定为早期的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.