x86 寻址模式必须符合[base + idx*scale + disp0/8/32] 的形式。 (或 RIP 相关的。)
*scale 实际上被编码为 2 位移位计数,因此它可以是 1、2、4 或 8。参见 A couple of questions about [base + index*scale + disp] 和 Referencing the contents of a memory location. (x86 addressing modes)
这里发生的事情是你的汇编器分解了[lst + rsi*5]
为您输入[lst + rsi + rsi*4]。(或1 + (1<<0..3) 形式的其他比例因子)
(其中lst 是一个 4 字节(32 位)绝对地址,可以符号扩展为 64 位。是的,这适用于Linux non-PIE executables;静态代码+数据恰好位于虚拟地址空间的低 2GiB 中所以这可以工作。)
但是,如果您已经有一个基址寄存器,则无法将其拆分并仍然具有可编码的寻址模式。 [rbx + rsi + rsi*4] 是不可能的。
同样,NASM 和 YASM 允许您编写 vaddps xmm0, [rbp] 之类的东西,而不是 vaddps xmm0, xmm0, [rbp+0](即使 RBP 作为基址寄存器是 not encodeable without a displacement。当它与目标相同时也省略第一个源操作数)。或者例如写 [rbp + rax] 而不是 [rbp + rax*1] - 寻址模式每个基数或索引最多只能有 1 个。
当您的代码表达的操作是明确的并且以某种方式可编码时,汇编程序有时具有方便的功能,可以让源代码看起来不同于机器代码/您从反汇编中获得的代码。
mov 和乘法是两种不同的 CPU 操作
寻址模式做包括加法和移位,尽管shl 和add 也是单独的指令。这不是为什么。此外,imul ecx, [lst + rsi + rsi*4], 12345 是有效指令。使用内存源或目标操作数进行类似的移位或加法也是如此。
但是是的,x86 寻址模式不能编码任意乘法,只能编码 2 位移位计数。
循环遍历任意步幅/元素大小的数组:
通常你会在寄存器中得到一个指针并在循环内递增它
add rsi, 5*4 ; 5*4 = 20 as an assemble time constant expression
add eax, [rsi]
这基本上是缩放的强度降低,将乘法或转换为加法。这意味着您可以使用更有效的简单非索引寻址模式(代码大小,并避免 Sandybridge 系列的分层。)