GCC 内联汇编乘法

5
我正在学习Linux(x86)上的GCC内联汇编,我的第一个实验是尝试为乘法实现整数溢出检测。这似乎很容易,但它会产生我不理解的副作用。
所以,我想要将两个无符号8位整数相乘,并查看结果是否溢出。基本上,我只需将第一个操作数加载到AL寄存器中,将另一个操作数加载到BL寄存器中,然后使用mul指令。结果存储为16位值在AX寄存器中。因此,我将AX寄存器中的值复制到我的C变量b中,除非它溢出。如果它溢出,我将c设置为1。
 uint8_t a = 10;
 uint8_t b = 25;
 uint8_t c = 0; // carry flag

 __asm__
 (
  "clc;"                  // Clear carry flag
  "movb %3, %%al;"        // Load b into %al
  "movb %2, %%bl;"        // Load a into %bl 
  "mul %%bl;"             // Multiply a * b (result is stored in %ax)
  "movw %%ax, %0;"        // Load result into b
  "jnc out;"              // Jump to 'out' if the carry flag is not set
  "movb $1, %1;"          // Set 'c' to 1 to indicate an overflow
  "out:"
  :"=m"(b), "=m"(c)       // Output list
  :"ir"(a), "m"(b)        // Input list
  :"%al", "%bl"           // Clobbered registers (not sure about this)
 );

这似乎运行得很好。如果我打印“b”的值,我会得到250,这是正确的。此外,如果我将“b”的起始值更改为26,那么在乘法后,“c”将设置为1,表示溢出,因为当然(10 * 26 > ~uint8_t(0))。我看到的问题是,在乘法后,C变量“a”被设置为0(或在溢出时为1)。我不明白为什么我的任何操作都会改变“a”的值。它甚至不在输出变量列表中,所以为什么我的汇编程序会影响“a”的值?
另外,我对被破坏的寄存器列表不确定。这个列表应该通知GCC有关在汇编程序期间使用的任何寄存器,以便GCC不会尝试不正确地使用它们。我认为我需要告诉GCC我使用了AL和BL寄存器,但AX寄存器呢?它隐式用于存储两个8位整数的乘积,所以我需要将它包含在破坏寄存器列表中吗?

2
不要将值加载到%al%bl中并标记它们被破坏,而应该设置约束条件,让gcc一开始就将参数放入正确的寄存器中。 - R.. GitHub STOP HELPING ICE
2个回答

7
我看到的问题是C变量a在乘法运算之后被设置为0(或者在溢出时为1)。我不明白为什么我的任何操作都会改变a的值。它甚至不在输出变量列表中,那么为什么我的汇编程序会影响a的值呢? mul %%bl将AL(8位)和BL(8位)相乘,将结果放入AX(16位)中。
请注意,AL和AX不是单独的寄存器:AL只是AX的底部8位。 movw %%ax,%0将AX(16位)存储到b的地址中...它是一个uint8_t。因此,这条指令也会用结果的顶部8位覆盖内存中的下一个字节。在这种情况下,该字节恰好是存储a的值的地方(这就解释了为什么当它不溢出时,a被覆盖为0,而在溢出时为1)。
你需要将其替换为movb %%al,%0,以仅存储结果的底部8位。
我认为我需要告诉GCC我使用了AL和BL寄存器,但是AX寄存器呢?它被隐含地用于存储两个8位整数的乘积,那么我需要在破坏的寄存器列表中包括它吗?
是的-你应该告诉GCC你改变了任何寄存器的值(正如nategoose在另一个答案中指出的那样,你可能还应该告诉它你正在改变标志)。所以这里的破坏列表应该是"%ax","%bl","cc"(AX包括AL,所以你不需要明确提及AL)。

2
你应该使用-S选项编译代码并查看*.s文件。你的所有汇编都在同一行上,用分号分隔,我相信分号在gnu汇编器中表示注释。你需要在所有汇编指令的结尾添加"\n"(最好是"\n\t")。
你可能还想将"cc"添加到clobber列表中。
此外,GCC有一种方法可以指定输入既是输入又是输出到汇编,你可能会感兴趣。
此外,最好让GCC决定输入的位置,而不是强制将其放入内存。我记得GCC的内联汇编在x86上有一个约束条件,即“寄存器或内存”,适用于不重要的情况,但你可能不应该在内联汇编的开头和结尾有太多的"mov"指令,因为GCC的主要工作之一是确定在实际计算指令之间放置最佳的移动指令集。例如,在你的代码中,对于GCC来说,最好的做法就是直接将常量10和25存储在你一开始使用的寄存器中。

1
GNU汇编器的语法取决于目标平台。在x86中,分号可以很好地作为分隔符,但在其他地方可能不行。 - Matthew Slattery

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