Assembly 为什么在x86汇编中分配64位?

Assembly 为什么在x86汇编中分配64位?,assembly,x86,Assembly,X86,为什么idivx86汇编指令将EDX:EAX(64位)除以给定的寄存器,而其他数学运算(包括乘法)仅对单个输入和输出寄存器进行操作 乘法: mov eax, 3 imul eax, 5 分部: mov edx, 0 mov eax, 15 mov ebx, 5 idiv ebx 我知道EDX用于存储剩余部分,但为什么没有针对此行为的单独说明?在我看来这似乎不一致。还有一个“双宽度”乘法(单操作数mul或imul) 如果你问“为什么没有只给出商的两个操作数idiv”,那么我真的不知道(我有一个

为什么
idiv
x86汇编指令将
EDX:EAX
(64位)除以给定的寄存器,而其他数学运算(包括乘法)仅对单个输入和输出寄存器进行操作

乘法:

mov eax, 3
imul eax, 5
分部:

mov edx, 0
mov eax, 15
mov ebx, 5
idiv ebx
我知道
EDX
用于存储剩余部分,但为什么没有针对此行为的单独说明?在我看来这似乎不一致。

还有一个“双宽度”乘法(单操作数
mul
imul

如果你问“为什么没有只给出商的两个操作数
idiv
”,那么我真的不知道(我有一个理论,但我不是为英特尔工作),我也希望这个存在


当你想用一个不是二的幂的模来进行模乘时,你可以做一个
mul
,然后直接用一个
div
,一切都已经准备好了。这是一个结果,而不是一个原因,出于这个原因,我们不得不问英特尔。。但这里有一个理论。早在8086年代,只有双倍宽度乘法(这是一种缓慢的迭代乘法,早期退出,就像在软件中一样)。后来,在80286上,他们增加了一些更灵活的乘法,但除法却从来没有这样做过。也许它没有那么紧迫——毕竟,除法相对较少,而您通常需要用小常量进行乘法,例如为结构数组编制索引。

指令集提供了有效实现任意宽度整数算法所必需的指令。对于加法和减法,除了固定宽度的结果之外,您所需要知道的就是该操作是产生进位(用于加法)还是借用(用于减法)。这就是为什么会有一面携带旗。对于乘法,您需要能够将两个单词相乘并得到一个双单词的结果。这就是为什么
imul
edx:eax
中生成其结果的原因。对于除法,您需要能够将一个双倍宽度的数字除法,并得到商和余数

要了解为什么需要这些特定的操作,请参阅Knuth的《计算机编程的艺术》,第2卷,其中详细介绍了实现任意宽度算法的算法


至于为什么x86指令集中没有更多不同形式的乘法和除法指令,没有二次幂的乘法和除法比其他指令要少见得多,因此英特尔可能不想使用可能用于更频繁使用的指令的操作码。一般用途程序中的大多数乘法和除法都是二的幂;对于这些,您可以使用位移位或lea指令代替。

对于加法和减法,溢出是由进位标志处理的单个位。如果要获取两个任意N位操作数并将其相乘,则需要2*N位来存储结果,非常简单,请自己尝试0xFF*0xFF=0xFE01。如果只使用N位大小的寄存器,乘法指令将非常有限。除法与乘除2*N位相反,得到N位。如果您为N位*N位=2*N位数而烦恼,那么您还应该实现2*N位数/N位数=N位数。这就是为什么它存在的原因,不幸的是,尽管硬件比语言做的更多,但语言也应该知道并做到这一点,如果我将两个字节相乘,如果我的结果变量小于16位,编译器应该抱怨精度。同时,任何使用加法、减法、乘法或除法运算的程序员也应该注意溢出,并且使用这些语言时使用的变量是操作数宽度的两倍,这样它们就不会溢出…

这里有两个问题。首先,存在双宽度输入或输出的问题,您忽略了执行全加宽乘法的一个操作数/形式,包括结果的高半部:N*N=>2N位,执行
EDX:EAX=EAX*src
。请参阅其他答案以了解这一点的用处

BMI2甚至引入了更灵活的全乘法指令,它有三个显式操作数(两个输出和一个输入),只有一个隐式操作数(第二个源=EDX)


其次,您给出了一个使用立即数操作数的示例,该操作数对于DIV/IDIV也不可用,但没有人提到过。

有一条模糊指令实际上是一个立即数div,执行8位/imm8=>8位商/余数,而不是16/8=>8。它被调用,在64位模式下不可用。汇编程序默认为除以10(对于BCD的预期用例),但与任何imm8都是相同的操作码,还指出了AAM和
DIV r/m8
之间的许多细微差别

英特尔本可以随时添加IDIV的即时版本,但从未添加过。我的猜测是DIV/IDIV足够慢(而且非常罕见),以至于
mov reg,imm32
的额外开销可以忽略不计,而且在这样的指令上花费操作码空间(和解码器晶体管)从来都不值得


更重要的是,实际的硬件除以编译时常数通常只对代码大小有用,而对性能不有用。自90年代以来,模块乘法逆一直是一个众所周知的概念(由编译器编写人员)。由于编译器甚至不使用常数除法,英特尔极不可能在这项技术问世后设计的CPU中为其添加指令。e、 g.clang编译
unsignedintdiv10(unsignedinta){returna/10;}

    mov     ecx, edi         # just to zero-extend to 64-bit
    mov     eax, 3435973837  # a sign-extended imm32 can't represent this constant, I guess.  clang uses imul r,r,imm for other cases.
    imul    rax, rcx         # 64-bit multiply instead of 32x32 => 64 in two separate regs
    shr     rax, 35          # extract part of the high-half result.
    ret
有符号除法需要更多的指令,有时一些加法/减法操作会影响结果