有没有一种简单的方法可以在 AT&T 汇编中像这样将两个寄存器相乘:%eax * %ebx = %ecx

Posted

技术标签:

【中文标题】有没有一种简单的方法可以在 AT&T 汇编中像这样将两个寄存器相乘:%eax * %ebx = %ecx【英文标题】:Is there an easy way to multiply two registers like this in AT&T assembly: %eax * %ebx = %ecx 【发布时间】:2019-09-12 16:10:12 【问题描述】:

我正在尝试使用 AT&T/GAS 语法将此 for 循环从 C 转换为程序集:

for(int j = i; i*j < N; j++) 
    A[i*j] = 0;

我有 i 存储在 %eaxj 存储在 %ebx。我遇到的问题是实际上将 ij 相乘,作为指令 imul "reg32", "reg32" 将结果存储在第二个寄存器中,这显然是我不想要的。我想要的是能够将结果存储在另一个寄存器中,例如 %ecx,然后使用它来访问数组中索引 i*j 处的值。

当我查看指令 imul 的用法时,似乎没有办法实际将两个寄存器相乘并将结果存储在第三个寄存器中。当然,我可以做一个循环并做一些加法等等,但这似乎无效,而且不是解决这个问题的方法。请注意,我对组装完全陌生(只使用了几天),因为我们刚刚开始在我的 CS 课程中学习基础知识。

TL;DR

将存储在两个寄存器中的值相乘的最佳方法是:%eax * %ebx = %ecx?

【问题讨论】:

指令 imul "reg32", "reg32" 将结果存储在第二个寄存器中,这显然是我不想要的。 为什么很明显你不想要这?正确的做法是imul %eax, %ebx 然后,如果您需要%ecx 中的结果,您可以使用mov %eax, %ecx。如果您需要保留 %eax 中的值,您可以保存它(例如,在堆栈上)。 当我查找指令 imul 的用法时,似乎没有办法实际将两个寄存器相乘并将结果存储在第三个寄存器中。 这是真的。但是解决起来并不麻烦。 【参考方案1】:

x86 是一个双操作数架构,其中大多数指令采用两个操作数,并覆盖其中一个。如果要将结果写入第三个操作数而不是覆盖其中一个源操作数,标准解决方案是首先将其中一个操作数移动到目标,然后将目标与两个操作数指令一起使用。例如,将eaxebx 相乘,将结果放入ecx,您会这样做

mov %ebx, %ecx
imul %eax, %ecx

尽管正如其他人指出的那样,对于您的循环,最好完全放弃乘法,而是认识到您可以使用加法。你的循环

for (int j = i; i*j < N; j++) 
    A[i*j] = 0;

可以改写为

A_ = A + i * i;
N_ = N - i * i;
for (j = 0; j < N_; j += i)
    A_[j] = 0;

循环内不需要乘法。

【讨论】:

【参考方案2】:

当我查看指令 imul 的用法时,似乎没有办法实际将两个寄存器相乘并将结果存储在第三个寄存器中。

大多数 x86 指令都是如此——大多数算术和逻辑运算采用两个操作数并将结果存储回源寄存器之一。如果您需要保存其中一个原始值,请将其复制到另一个寄存器。

imul 是一个特别奇怪的 x86 指令,因为它有一个单参数形式,它将源寄存器乘以eax,并将结果写入edx:eax。这些寄存器映射不灵活;如果您需要完整的产品,则需要围绕此分配您的寄存器。

当然,我可以做一个循环并做一些加法等等,但这似乎无效,而且不是解决这个问题的方法。

这实际上是一个很好的方法——加法比乘法快。一个好的优化编译器可能会按照这些思路做一些事情。

【讨论】:

imul 单操作数形式,但通常只有在想要高半结果时才应使用该形式。它速度较慢(需要额外的 uop 来拆分结果并将高半部分写入 EDX),并且通常需要额外的指令来从累加器获取数据/从累加器获取数据。如果您只想要一个正常的截断 32x32 => 32 位乘法,请使用 2 操作数形式。 (或者使用立即数,3 操作数 imul r32, r/m32, imm)您的措辞似乎暗示您实际上会使用 imul r/m32 而不是 imul r32, r/m32,但您只能在 16 位代码中为 8086 兼容这样做。 felixcloutier.com/x86/imul【参考方案3】:

您要观察的是i*j 随着您增加j 而变化的方式。所以,让我们假设i50,然后最初是j = 50,所以i*j50*50。循环的下一次迭代,j51,所以 i*j50*51,或者,50*(50+1),或者,50*50+50。而接下来的迭代,i*j 就是50*50+50+50,以此类推。

通过保留一个累加器,在 j 循环外部/之前使用 i*i 进行初始化,并在每次循环迭代中使用一条简单的加法指令进行维护,您可以在不进行乘法运算的情况下获得 i*j 的值。

另见induction variable。

强烈怀疑如果您查看外部 i 循环(问题中未显示),您将能够消除初始乘法(这里是第一个 i*i 初始化累加器)。

【讨论】:

以上是关于有没有一种简单的方法可以在 AT&T 汇编中像这样将两个寄存器相乘:%eax * %ebx = %ecx的主要内容,如果未能解决你的问题,请参考以下文章

AT&T x86_32 汇编_001_一个示例程序.md

试图在汇编 at&t 中编写 strcpy,没有输出

Linux下AT&T汇编语法格式与Intel汇编语法格式异同

英特尔 AT&T 汇编程序的逐步执行?

AT&T汇编语言及其寻址方式

使用 AT&T 语法时汇编程序崩溃 [重复]