程序集 - 在 register/array 中移动,偏移量为 5

Assembly - Moving through a register/array with an offset of 5

快速提问。此代码将无法编译:

mov eax, dword [rbx+rsi*5]

我不希望如此,解释说 mov 和乘法是两种不同的 CPU 操作。它可以实现的唯一原因是通过位移。

然而,这确实编译:

mov eax, dword [lst+rsi*5]

其中"lst"为可变数组。它还在上下文中使用时产生输出(因此代码编译并运行)。为什么这有效?

yasm -Worphan-labels -g dwarf2 -f elf64 NAME.asm -l NAME.lst

x86 寻址模式必须符合 [base + idx*scale + disp0/8/32] 的形式。 (或相对于 RIP。)

*scale实际上编码为2位移位计数,所以可以是1,2,4,8。见 and

这里发生的是你的汇编程序分解 [lst + rsi*5]
为您输入 [lst + rsi + rsi*4]
(或 1 + (1<<0..3) 形式的其他比例因子)
(其中 lst 是一个 4 字节(32 位)绝对地址,它被符号扩展为 64 位。是的,这在 Linux non-PIE executables 中有效;静态代码+数据进入低 2GiB虚拟地址 space 正是这样才能工作。)

但是如果你已经有一个基址寄存器,就没有办法将它拆分并且仍然有一个可编码的寻址模式。 [rbx + rsi + rsi*4]不可能。

类似地,NASM 和 YASM 允许您编写类似 vaddps xmm0, [rbp] 而不是 vaddps xmm0, xmm0, [rbp+0] 的东西(即使作为基址寄存器的 RBP 是 。也省略了第一个当源操作数与目标相同时)​​。或者例如写 [rbp + rax] 而不是 [rbp + rax*1] - 寻址模式每个基数或索引最多只能有 1 个。

当您的代码表达的操作明确且可编码时,汇编程序有时会提供方便的功能,使源代码看起来不同于机器代码/您从反汇编中获得的内容。


mov and multiplication are two different CPU operations

寻址模式 do 包括加法和移位,尽管 shladd 也是单独的指令。这不是原因。此外,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 系列上分层。)