c-linux汇编程序如何对64位数字进行乘法和除法运算



我有一项大学监督任务,要用汇编程序编写一个函数,

需要三个32位无符号数字a、b、d和

返回结果(a*b)/d

此函数的c声明为:

unsigned int muldiv(const unsigned int a, 
                    const unsigned int b, 
                    const unsigned int d);

请注意,我们希望确保不会发生不必要的上溢或下溢。例如,

如果a=2^31,b=2^31、d=2^31,

答案应该是2^31,尽管事实上a*b会溢出。(见下文的更多说明)

现在我用c编写了一个简单的函数,它可以工作,然后将它编译成机器代码,然后反汇编成汇编代码,最后删除了一些不必要的指令。

我的最后一段汇编代码是:

muldiv:
    imulq   %rsi, %rax
    xorl    %edx, %edx
    divq    %rcx
    ret  

当编译成可执行代码并在几个测试用例中检查时,它就可以工作了。然而,我并不完全理解这段代码中发生了什么。

因此,有人能解释一下为什么这个代码有效(或者可能无效?),特别是:

  • 为什么divq%rcx指令只使用一个寄存器?我假设这是除法部分,那么它如何知道要使用哪个两个参数呢
  • 它怎么知道当我从另一个地方调用muldiv时,参数a、b和d存储在寄存器%rsi/%rax/e.t.c中,而不是其他地方
  • 为什么xorl%edx,%edx是必要的?删除后,会出现运行时错误。

  • 若机器只能对32位数字进行运算,那个么它如何只使用一条指令对长-长数字进行乘法运算?

溢流和底流澄清:此函数应返回结果,就好像我们在操作无符号64位数字一样。c中的代码如下:

// NOTE: when compiled to assembly code, I removed A LOT of instructions,
// but it still works
unsigned int muldiv(const unsigned int a, 
    const unsigned int b, 
    const unsigned int d) {
    const unsigned long long la = a;
    const unsigned long long lb = b;
    const unsigned long long ld = d;
    const unsigned long long ab = la * lb;
    const unsigned long long ab_over_d = ab / ld;
    return (unsigned int) ab_over_d;
}

当以这样的方式调用时,它起了作用:

#include "muldiv.h"
int main(void) { 
   unsigned int a = (1 << 31);
   unsigned int b = (1 << 31);
   unsigned int d = (1 << 31);
   unsigned int result = muldiv(a, b, d);
   printf("%un", result);  // prints (1 << 31), which is correct.
   return 0;
}

为什么divq%rcx指令只使用一个寄存器?我假设这是除法部分,那么它怎么知道该使用哪两个自变量呢?

它使用来自寄存器对rdx:rax的隐式128位被除数。有关操作的说明,请参阅指令集参考。

它怎么知道当我从另一个地方调用muldiv时,参数a、b和d存储在寄存器%rsi/%rax/e.t.c中,不是别的地方?

这是由调用约定定义的。有关摘要,请参阅维基百科。

为什么xorl%edx,%edx是必需的?删除后,会出现运行时错误。

见上文第1点。rdx有被除数的前64位,所以应该清除它进行无符号除法。

它是如何在长-长数字上只使用一个进行乘法的指令,如果机器只能在32位数字上操作?

您使用了64位模式,所以这不是真的。此外,mul和div指令具有双倍宽度的风格,因此您甚至可以在64位模式下获得128位版本,在32位模式下得到64位版本。同样,请参阅指令集参考。

恐怕这是一个部分答案,我需要在有时间的时候再看一次(工作电话!),但希望能有所帮助:-

为什么divq%rcx指令只使用一个寄存器?我假设这是除法部分,那么它怎么知道该使用哪两个自变量呢

http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf第2A卷3-257

对AX、DX:AX、EDX:EAX或RDX:RAX中的值进行无符号除法按源操作数(除数)进行寄存器(被除数),并存储导致AX(AH:AL)、DX:AX、EDX:EAX或RDX:RAX寄存器。这个源操作数可以是通用寄存器或内存位置。此指令的操作取决于操作数大小(被除数/除数)。使用64位操作数的除法仅在中可用64位模式。

非积分结果被截断(截断)为0。余数的大小总是小于除数。溢流用#DE(除法错误)异常指示,而不是用CF标志

它怎么知道当我从另一个地方调用muldiv时,参数a、b和d存储在寄存器%rsi/%rax/e.t.c中,而不是其他地方

这是特定于编译器和环境的。您需要查找环境的ABI:什么是应用程序二进制接口(ABI)?

为什么xorl%edx,%edx是必需的?删除后,会出现运行时错误

它将DX寄存器清零。

如果机器只能对32位数字进行运算,那么它如何只使用一条指令对长数字进行乘法运算

RAX寄存器是一个64位寄存器。那么,你为什么认为这台机器是在32位数字上运行的呢?也许您的测试用例需要扩展?

最新更新