我编写了以下 C 程序:
#include "stdio.h"
__declspec(noinline) void DivideTest(int num, int denom)
{
int quo = num / denom;
int rem = num % denom;
printf("Quotient: %d\nRemainder: %d\n", quo, rem);
}
int main(int argc, char* argv[])
{
//Use volatile variables to prevent result from being hardcoded.
volatile int num = 20;
volatile int denom = 3;
DivideTest(num, denom);
return 0;
}
正如预期,输出如下:
Quotient: 6
Remainder: 2
但是,当我在发布模式下进行编译(即启用优化)时,在 Visual Studio 中调试程序并查看反汇编代码,它显示 DivideTest 使用双操作数 idiv:
_DivideTest:
push esi
mov esi,edx
mov eax,ecx
cdq
idiv eax,esi
使用 dumpbin 进行反汇编会产生相同的结果。但是我发现的每个来源( 示例)都说 idiv 只能接受一个操作数。
当我尝试使用双操作数 idiv 汇编代码时,它失败了,正如我根据文档所预料的那样。为什么反汇编显示双操作数 idiv?
从技术上讲,反汇编是错误的。它只是展示了
idiv
工作原理。该idiv
指令始终使用eax
(实际上edx:eax
是一对)来获取被除数并存储结果(商和余数)。您的反汇编程序(Visual Studio 调试器?)正在发明自己的 asm 语法来描述
idiv
。在机器代码中,EDX:EAX 操作数(被除数输入和 rem:quo 输出)是隐式的,由操作码暗示,这就是为什么无法选择不同的寄存器。
主流 asm 语法(包括 AT&T,以及Intel和 AMD 的手册)反映了这种选择,使其成为仅具有显式除数的单操作数指令。
但是没有理由不能要求汇编语言将 EAX 作为第一个操作数,例如,当存在内存源操作数时,作为设置操作数大小的一种方式。(例如,
idiv eax, [ecx]
而不是idiv dword ptr [ecx]
)。MASM 已经为rep movsd
vs实现了这一点rep movs es:[edi], ds:[esi]
,英特尔实际上记录了这一点,请注意说明中的第 2 和第 3 段。另请参阅汇编:`stos m32` 和 `stosd` 助记符之间有什么区别?另一个例子。有点奇怪的是,它只使用 EAX 而不是 EDX:EAX 或 edx,eax,因为它无论如何都在制定自己的语法。当还有一个隐式寄存器操作数时,将一个寄存器操作数显式化有什么意义呢?我想您可能会争辩说 EDX:EAX 是一对由其下半部分寻址的对。
当然,如果反汇编程序使用的语法与你正在使用的汇编程序相同,那么阅读反汇编程序并将其复制/粘贴到你正在处理的 asm 源文件中会更容易。我不知道 MASM 是否接受这一点。如果接受,它应该只接受
eax
(或rax
/ax
/al
) 作为第一个操作数。(我听说过一些糟糕的汇编程序,它们接受误导性操作数作为隐式操作数的占位符,或者将不可编码的指令汇编成执行不同操作的机器代码。)这与CS:APP 示例使用带有两个操作数的 idivq有什么不同? ——这是一个完全伪造的汇编,据称由 GCC 生成,但实际上是由出版商雇佣的一些小丑编造的,目的是搞砸 CS:APP 3e 全球版中的练习问题。在这种情况下,他们展示了一个带有直接源和 RCX 目标的表单,这两者都不可能!