3

在 x86 汇编器中,假设您有

  • 分配号码的立即寻址模式
  • 寄存器的寄存器寻址模式
  • 内存地址的直接寻址模式

为什么需要索引和基指针寻址模式?据我所知,每个都可以用循环代替。

此外,间接模式似乎也不是很有用,因为您可以简单地使用直接模式来引用内存地址。首先访问一个寄存器然后包含一个指向内存地址的指针的目的是什么?

简而言之,哪些寻址模式是真正需要的?

4

2 回答 2

8

虽然理论上“寻址模式”可以用来指代操作数类型,但由于它不涉及地址,所以有点令人困惑。英特尔手册使用“寻址模式”来指代内存寻址,我将使用此定义。

在汇编中,操作数可以是:

  • 直接价值
  • 一个寄存器
  • 内存中的一个值(这里的操作数是地址)

在x86架构中,“寻址方式”只针对最后一种操作数:内存操作数(地址),指的是可用于计算地址的方法。寻址模式可以总结为一个可配置的寻址模式:

address = REG_base + REG_index*n + offset

REG_base, REG_index,noffset都是可配置的,并且都可以省略(但显然你至少需要一个)。

address = offset称为立即、直接或绝对寻址。
address = REG_base称为寄存器间接寻址。
address = REG_base + REG_index称为基址加索引寻址。
同样,您可以添加偏移量 ( offset) 和比例 ( n)。

严格来说,你只需要一种模式就可以完成所有事情:注册间接寻址(address = REG)。这样,如果您需要访问内存,您可以在寄存器中计算您想要的任何地址,并使用它来进行访问。它还可以通过使用内存来代替直接寄存器操作数,并通过用算术构造值来替换立即操作数。但是,对于实际的指令集,您仍然需要立即操作数来有效地加载地址,并且如果您不想要仅指针的寄存器,则需要寄存器操作数。

为了方便起见,除了寄存器间接之外的所有其他寻址模式都在这里,它们确实非常方便:

  • 如果您只需要访问内存中的固定变量,立即寻址可以为您节省一个寄存器。
  • Base + offset 对于访问对象成员非常有用:您可以将基地址保存在寄存器中并使用固定偏移量访问单个成员。无需中间计算或注册来保存成员地址。
  • 类似地,索引寻址用于访问数组:您只需更改索引寄存器即可访问数组中的任何值。
  • 使用比例,您可以访问多字节变量(例如:)int数组,而无需额外的寄存器或计算。
  • 可以使用所有内容的组合来访问对象中的数组成员,同时保留基指针以供可能访问对象中的其他成员。

这些寻址模式不需要 CPU 进行太多计算:只需要加法和移位。考虑到 x86 可以在每个循环中进行乘法运算,这些运算虽然微不足道,但仍然非常方便。

于 2016-02-05T11:31:31.653 回答
1

您只需要一种内存寻址模式,这就是许多经典 RISC 机器在实践中所做的,例如 MIPS、SPARC、RISC-V。他们选择immediate($register)是因为在固定宽度的指令中有立即的空间。(有些像 PowerPC 有单独的索引寻址指令,否则你必须在寄存器中进行数学运算来计算你想要的地址。)


x86 没有寄存器就不能做很多事情,所以我认为你不能摆脱寄存器“寻址模式”。一些非常不同的架构可能不使用寄存器,而只有堆栈或内存、内存指令。IDK 他们如何实现指针;也许这样的架构可以做到memory[memory](C 数组表示法)。

无需立即进行计算即可。您可以使用多个寄存器构造任何值。从零(xor eax, eax)开始,inc它得到一个 1,将其左移到您想要的任何位置,inc设置低位,左移,等等。因此,进入寄存器需要最坏的2*popcount(N)指令。N请注意,即时移位计数将不可用,因此重复移位一个的明显方法(shl eax是的,移位一个单独的编码,或者只是使用add eax, eax)将仅取决于最高的位置设置位。所以log2(N) + popcount(N)对于明显的转变和公司。

绝对(你称之为直接)内存寻址不是最有用的寻址模式。我们可以通过使用一系列指令(见上文)构造地址来模拟它,并使用[register]. 如果我们想减少,我们想放弃它。正如 Jester 所指出的,将绝对寻址作为我们唯一的形式使用起来会非常不方便(或者可能是不可能的?)。

索引显然可用于性能,而不是必需品:您可以使用单独的指令进行移位和添加。

位移也只是为了性能,所以我们可以去掉它们并强制代码手动添加任何位移。请参阅立即段落了解如何操作。


我相信 x86 仍然可以通过just register[register]寻址模式任意编程。

使用register,[register]immediate, 性能应该不会比完整的 x86 差很多,尽管寄存器压力会更糟,因为您需要备用 regs 来更频繁地计算地址。当然,还需要更多说明。

如果您打算只有一种内存寻址模式,那么这是一个比 RISC 使用的更有用的选择。这有时会通过不必计算另一个寄存器中的地址来节省指令(和备用寄存器),并且仍然允许 LEA 复制和添加一个常量,尽管它目前没有移位和添加两个 reg 的能力。[reg][reg+disp]

对于 x86 当前的机器码格式,它需要一个额外的字节(或 4 个字节)的机器码。信令reg直接与 - 间接[reg](内存)与[reg+disp8]vs.[reg+disp32]使用 ModRM 字节中的 2 位。


如果对内存的隐式访问不算作寻址模式,您当然可以[register]使用lodsdand进行模拟stosd,但您将无法执行原子读取-修改-写入操作。不过,这感觉像是作弊。

还有堆栈(push/pop):我不知道堆栈+寄存器机器是否是图灵完备的,但它肯定不是通常意义上的可编程。当然,如果您修改e/rsp,您可以再次模拟[register],但操作数大小的选择比lodsb/w/d/q/少stosb/w/d/q

如果包含 16 个 ymm 寄存器,x86 有相当多的空间可以在寄存器中存储东西。虽然我想不出一种在不使用内存或立即操作数(for vextractf128)的情况下在整数寄存器和 ymm 的高 128b 之间移动数据的方法,但实际上你有更多类似于 16 个 16B 向量寄存器插槽来存储本地状态除了堆栈。尽管如此,它的大小还是有限的,这可能意味着 32 位 386 ISA 中的 8 个 GP 寄存器与 64 位 AVX2 ISA 中的所有整数/mmx/ymm 寄存器与机器是否图灵完备无关,只有 push/pop ,寄存器,并且除了通过 push/pop 之外没有修改堆栈指针。

于 2016-02-06T08:47:04.510 回答