gcc内联汇编错误“mov操作数类型不匹配”

3
//quick inline asm statements performing the swap_byte for key_scheduling
inline void swap_byte(unsigned char *x, unsigned char *y)
{
 unsigned char t;
 asm("movl %1, %%eax;"
     "movl %%eax, %0;"
     :"=r"(t)
     :"r"(*x)
     :"%eax");
 asm("movl %1, %%eax;"
     "movl %%eax, %0;"
     :"=r"(*x)
     :"r"(*y)
     :"%eax");
 asm("movl %1, %%eax;"
     "movl %%eax, %0;"
     :"=r"(*y)
     :"r"(t)
     :"%eax");       
}

我在尝试将x中的字符与y中的字符进行交换,并且同样地将y中的字符与x中的字符进行交换。

我已经通过将movl更改为mov来编译这些指令,但没有成功。编译/链接的问题出在哪里?

以下是在cygwin中编译的输出结果:

$ gcc rc4_main.c -o rc4ex
/tmp/ccy0wo6H.s: Assembler messages:
/tmp/ccy0wo6H.s:18: Error: operand type mismatch for `mov'
/tmp/ccy0wo6H.s:18: Error: operand type mismatch for `mov'
/tmp/ccy0wo6H.s:26: Error: operand type mismatch for `mov'
/tmp/ccy0wo6H.s:26: Error: operand type mismatch for `mov'
/tmp/ccy0wo6H.s:34: Error: operand type mismatch for `mov'
/tmp/ccy0wo6H.s:34: Error: operand type mismatch for `mov'

2
如果这是生产代码,并且您正在尝试加速RC4或其他密码的密钥调度循环,则在此处使用内联汇编只会使您的代码变得更慢。最好使用普通的C语言来执行交换,因为编译器能够更好地进行优化。 - Ross Ridge
3个回答

6
为了更简化它(比user35443说的还简单):
asm("" : "=r" (*x), "=r" (*y) : "1" (*x), "0" (*y));

看啊!没有代码!是的,这真的有效。

解释一下它的工作原理:

当编译器构建代码时,它会跟踪每个寄存器中的值。所以如果输入到汇编语言中有以下内容:

"r" (*x), "r" (*y)

编译器将选择一个寄存器并将 *x 放入其中,然后选择另一个寄存器并将 *y 放入其中,然后调用你的汇编语言。但它还要跟踪哪个变量在哪个寄存器中。如果有一种方法可以告诉编译器它只需要开始将这两个寄存器视为相反的变量,那么我们就完成了。而这正是这段代码所做的:

  1. 说 "=r" (*x) 意味着我们将覆盖 *x 中的值,并将该值放入寄存器中。
  2. 说 "0" (*y) 意味着在输入到汇编语言时,编译器必须将 *y 的值放入与输出参数 #0 使用的相同寄存器中。

因此,我们不需要使用任何实际的汇编指令,就已经告诉编译器交换了这两个值。

我们并不完全“免费”得到这个结果,因为编译器必须在调用汇编语言之前将值加载到寄存器中。但由于这必须发生...

那么更新内存呢?编译器将(如果必要)从寄存器中将这些值写回内存。由于它知道哪个变量在哪个寄存器中,所以一切都按预期进行。


哈哈,我写了完全相同的零指令内联汇编交换实现来回答一个类似的关于交换C变量的问题。链接 - Peter Cordes

3
unsigned char t;
asm("movl %1, %%eax;"
     "movl %%eax, %0;"
     :"=r"(t)  /* <--here */
     :"r"(*x)  /* <-- and here */
     :"%eax");

您不能将一个32位寄存器中的值移动到一个单字节的内存位置。变量t在栈中,而变量x则在其他地方,但它们都是以同样的方式进行访问。其他行上出现的问题也相似。您应该只移动一个字节。
可以尝试类似以下的方法,但还有其他的做法(我没有尝试过,请看下面):
unsigned char t;
asm("movb %1, %%al\n"
     "movb %%al, %0\n"
     :"=r"(t)
     :"r"(*x)
     :"%al");
asm("movb %1, %%al\n"
     "movb %%al, %0\n"
     :"=r"(*x)
     :"r"(*y)
     :"%al");
asm("movb %1, %%al\n"
     "movb %%al, %0\n"
     :"=r"(*y)
     :"r"(t)
     :"%al"); 

整个过程可以简化为以下步骤:
asm("movb (%1), %%al\n"
    "movb (%2), %%ah\n"
    "movb %%ah, (%1)\n"
    "movb %%al, (%2)\n"
    : /* no outputs for compiler to know about */
    : "r" (x), "r" (y)
    : "%ax", "memory");

实际上,这个“简化”的代码是不正确的。你没有告诉编译器你正在改变任何东西。它无法知道 *x 和 *y 已经被更改了。 - David Wohlferd
是的,我忘记在最后添加“内存”了。顺便说一下,你的代码很不错 :) - user35443
还不对。应该是%0/%1。虽然添加“memory”限定符可以使其正常工作,但这并不是最好的解决方案。在像这样简单的代码中,可能无关紧要,但在任何大型项目中,这都会影响性能。使用“memory”可能会导致编译器将其存储在寄存器中的其他值刷新回内存,然后运行asm之前需要从内存重新加载到寄存器中。与我的备选答案中零行汇编相比,这增加了额外的开销。 - David Wohlferd
此外,当我的汇编代码退出时,编译器知道指定的值在已知的寄存器中。如果(很可能)它需要立即对这些值进行操作,它就不必再从内存中读取它们到寄存器中。根据这些值的使用方式,编译器可能决定永远不需要将它们刷新回内存,从而进一步提高性能。而且,如果在调用swap_byte之前这些值已经在寄存器中(或者是常量),我的代码可以直接使用这些值,而你的代码则总是强制进行内存写入/读取。 - David Wohlferd
1
你对我有什么期望?我来到这里,看到一个人用大写字母,并做出了努力写两个评论,只为了……什么? - user35443
2
抱歉,我不是想太过批评。我的目标不仅仅是回答这个人(可能已经离开了),而是回答所有在未来访问 SO 的其他人并发现这个答案的人。我认为 gcc 的内联汇编很有趣也很酷,但它也可能会棘手。它存在一些非明显的问题(例如“memory”的影响),人们可能不知道,所以我尽力传授自己所学的知识。 - David Wohlferd

-1

movl %%eax, %0; 

这完全是胡说八道!你试图通过%eax寄存器来改变0常量,这是不可能的。在很多年前的Fortran中,它是可以的。但之后所有的程序都会表现得非常不可预测。为了避免这种情况,规定任何标识符都不能以数字开头。但你却试图这样做。这样做肯定会出错。也许你的意思是另外一件事。

movl %0, %%eax; 

将零设置为eax。最好写另一段代码。

xorl %%eax, %%eax;

好多了!


movl %%eax, %0; 实际上并不改变一个常量。%0 使用了扩展 asm 约束提供的操作数 0。我认为你混淆了 %0$0,它们是完全不同的。 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接