linux汇编程序如何乘以和除以64位数?

时间:2015-11-12 16:31:12

标签: c assembly

我有一个大学监督分配,在汇编程序中编写一个函数,

将采用三个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("%u\n", result);  // prints (1 << 31), which is correct.

   return 0;
}

2 个答案:

答案 0 :(得分:4)

我害怕这是一个部分答案,当我有片刻(工作电话!)时,我需要再次查看它,但希望有所帮助: -

为什么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 / etc中,而不是其他地方?

这是编译器和环境特定的。您需要查找适合您环境的ABI: What is Application Binary Interface (ABI)?

为什么需要xorl%edx,%edx?删除后,我收到运行时错误。

将DX寄存器归零。

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

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

答案 1 :(得分:4)

  

为什么divq%rcx指令只使用一个寄存器?   我假设这是除法部分,那么它如何知道使用哪两个参数?

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

  

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

由调用约定定义的那个。有关摘要,请参阅wikipedia

  

为什么需要xorl%edx,%edx?删除后,我收到运行时错误。

见上文第1点。 rdx他的红利是64位,所以应该清除无符号除法。

  

如何仅使用一个长长数字进行乘法运算   指令,如果机器只能运行32位数字?

您使用64位模式,因此不正确。此外,mul和div指令具有双倍宽度的风格,因此您甚至可以在64位模式下获得128位版本,在32位模式下获得64位版本。再次,请参阅指令集参考。