【问题标题】:How linux assembler multiplies and divides 64 bit numbers?linux汇编器如何乘除64位数字?
【发布时间】:2015-11-12 16:31:11
【问题描述】:

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

需要三个 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 位数字进行运算,它如何仅使用一条指令对 long long 数字进行乘法运算?

上溢和下溢的说明: 这个函数应该返回结果,就好像我们在处理 无符号 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;
}

【问题讨论】:

标签: c assembly


【解决方案1】:

为什么 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 位版本。再次,请参阅指令集参考。

【讨论】:

    【解决方案2】:

    恐怕这有点片面,我需要有时间再看一遍(工作电话!),但希望能有所帮助:-

    为什么 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: What is Application Binary Interface (ABI)?

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

    它将 DX 寄存器归零。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      • 1970-01-01
      • 2014-01-09
      • 1970-01-01
      • 2018-07-18
      • 1970-01-01
      相关资源
      最近更新 更多