对strcpy的这种实现感到困惑,为什么需要三个临时变量?

时间:2013-01-25 08:36:45

标签: c gcc assembly x86 inline-assembly

更新:请参阅Eric Postpischil的回答,我认为他是对的。

当我研究内联汇编代码时,我在this page找到了它。

static inline char * strcpy(char * dest,const char *src)
{
    int d0, d1, d2;
    __asm__ __volatile__(  "1:\n\t"
                           "lodsb\n\t"
                           "stosb\n\t"
                           "testb %%al,%%al\n\t"
                           "jne 1b"
                           : "=&S" (d0), "=&D" (d1), "=&a" (d2)
                           : "0" (src),"1" (dest) 
                           : "memory");
    return dest;
}

我很困惑为什么需要三个临时变量?我尝试在不使用它们的情况下实现。

static inline char * strcpy(char * dest,const char *src)
{
    __asm__ __volatile__(  "1:\n\t"
                           "lodsb\n\t"
                           "stosb\n\t"
                           "testb %%al,%%al\n\t"
                           "jne 1b"
                           /* : "=&S" (d0), "=&D" (d1), "=&a" (d2) */
                           :
                           : "S" (src),"D" (dest) 
                           : "memory", "esi", "edi", "al");
    return dest;
}

但是用gcc编译时出错了。

inline.c: In function ‘strcpy’:
inline.c:6:9: error: can't find a register in class ‘SIREG’ while reloading ‘asm’
inline.c:6:9: error: ‘asm’ operand has impossible constraints

2 个答案:

答案 0 :(得分:2)

事情很简单。让我们回想一下,S约束代表esia代表alD代表edi注册。当您通知编译器时,那些人将遭到破坏,您必须明确指出泄漏他们的位置(d0esi的临时存储,d1edi,{{ 1}} d2)。

接下来在寄存器分配过程中,编译器决定他是否确实需要溢出并查找可用的寄存器。例如,我的编译器(gcc 4.7.2)按如下方式执行:

al

您可能会看到movq %rdi, %rdx 1: lodsb stosb testb %al,%al jne 1b movq %rdx, %rax ret 已分配给d1rdxd0被淘汰为过度。

如果您没有为编译器提供该信息,则无法猜测,因此输出错误。

答案 1 :(得分:2)

原始代码试图处理lodsbstosb指令修改寄存器%ESI,%EDI和%AL的事实。为此,它定义了d0d1d2,以便它可以将它们声明为汇编代码的输出。它实际上并不希望将这些值作为输出,因此在组装后它不会使用它们。但是,因为它们是输出,所以编译器知道它们被汇编代码修改,因此编译器知道不要在这些寄存器中保留它希望通过汇编代码保持不变的任何值。

应该可以以另一种方式执行此操作,并且您的代码更好地表达语义:它表示输入在%ESI和%EDI中提供,并且内存,%ESI,%EDI和%AL被更改。原始代码断言%ESI,%EDI和%AL是输出,这不是真正的意图;它们是不需要的副产品,而不是理想的产出。

但是,您的GCC版本与我的不同(Apple / clang-418.0.60);我接受你写的代码没有错误。我怀疑你的GCC被%ESI指定为输入寄存器(在第二个冒号之后由"S"指定)和在第三个冒号后被修饰(在"esi"之后)这一事实感到困惑。也许这是GCC中的一个缺点,后来修复了,原始代码正在解决这个缺点。