跳转到内容

x86 汇编/移位和旋转

来自维基教科书,开放的书籍,开放的世界


逻辑移位指令

[编辑 | 编辑源代码]

逻辑移位指令(也称为无符号移位)中,从末尾移出的位会消失(除了最后一个位进入进位标志),并且空格始终填充零。逻辑移位最适合用于无符号数。


shr cnt, dest GAS 语法
shr dest, cnt Intel 语法


dest逻辑右移cnt位。


shl cnt, dest GAS 语法
shl dest, cnt Intel 语法


dest逻辑左移cnt位。

示例 (GAS 语法)

movw   $ff00,%ax        # ax=1111.1111.0000.0000 (0xff00, unsigned 65280, signed -256) 
shrw   $3,%ax           # ax=0001.1111.1110.0000 (0x1fe0, signed and unsigned 8160)
                        # (logical shifting unsigned numbers right by 3
                        #   is like integer division by 8)
shlw   $1,%ax           # ax=0011.1111.1100.0000 (0x3fc0, signed and unsigned 16320) 
                        # (logical shifting unsigned numbers left by 1
                        #   is like multiplication by 2)

算术移位指令

[编辑 | 编辑源代码]

算术移位(也称为有符号移位)中,与逻辑移位类似,从末尾移出的位会消失(除了最后一个位进入进位标志)。但在算术移位中,空格的填充方式会保留被移位数字的符号。因此,算术移位更适合于二进制补码格式的有符号数。


sar cnt, dest GAS 语法
sar dest, cnt Intel 语法


dest算术右移cnt位。空格用符号位(以保持原始值的符号)填充,即原始最高位。


sal cnt, dest GAS 语法
sal dest, cnt Intel 语法


dest算术左移cnt位。底部的位不会影响符号,因此底部位用零填充。此指令等同于 SHL。

示例 (GAS 语法)

movw   $ff00,%ax        # ax=1111.1111.0000.0000 (0xff00, unsigned 65280, signed -256)
salw   $2,%ax           # ax=1111.1100.0000.0000 (0xfc00, unsigned 64512, signed -1024)
                        # (arithmetic shifting left by 2 is like multiplication by 4 for
                        #   negative numbers, but has an impact on positives with most
                        #   significant bit set (i.e. set bits shifted out))
sarw   $5,%ax           # ax=1111.1111.1110.0000 (0xffe0, unsigned 65504, signed -32)
                        # (arithmetic shifting right by 5 is like integer division by 32
                        #   for negative numbers)

扩展移位指令

[编辑 | 编辑源代码]

双精度移位操作的名称有些误导,因此在本页中它们被列为扩展移位指令。

它们可用于 16 位和 32 位数据实体(寄存器/内存位置)。src 操作数始终是寄存器,dest 操作数可以是寄存器或内存位置,cnt 操作数是立即字节值或 CL 寄存器。在 64 位模式下,也可以寻址 64 位数据。


shld cnt, src, dest GAS 语法
shld dest, src, cnt Intel 语法

shld 执行的操作是将 dest 中最重要的 cnt 位移出,但不是用零填充最低有效位,而是用 src 的最重要的 cnt 位填充。


shrd cnt, src, dest GAS 语法
shrd dest, src, cnt Intel 语法

类似地,shrd 操作将 dest 中最低有效的 cnt 位移出,并用 src 操作数的最低有效位填充最重要的 cnt 位。

英特尔的命名法具有误导性,因为移位不作用于双倍的基本操作数大小(即指定 32 位操作数不会使其成为 64 位移位):src 操作数始终保持不变。

此外,英特尔的参考手册[1] 指出,当 cnt 大于操作数大小时,结果是未定义的,但至少对于 32 位和 64 位数据大小,已观察到移位操作是通过 (cnt mod n) 执行的,其中 n 是数据大小。

示例 (GAS 语法)

xorw   %ax,%ax          # ax=0000.0000.0000.0000 (0x0000)
notw   %ax              # ax=1111.1111.1111.1111 (0xffff)
movw   $0x5500,%bx      # bx=0101.0101.0000.0000
shrdw  $4,%ax,%bx       # bx=1111.0101.0101.0000 (0xf550), ax is still 0xffff
shldw  $8,%bx,%ax       # ax=1111.1111.1111.0101 (0xfff5), bx is still 0xf550

其他示例(十进制数用于解释概念,而不是二进制数

# ax = 1234 5678
# bx = 8765 4321
shrd   $3, %ax, %bx     # ax = 1234 5678 bx = 6788 7654
# ax = 1234 5678
# bx = 8765 4321
shld   $3, %ax, %bx     # bx = 5432 1123 ax = 1234 5678

旋转指令

[编辑 | 编辑源代码]

在旋转指令中,从寄存器末尾移出的位会反馈到空格中。

ror offset, variable GAS 语法
ror variable, offset Intel 语法

variable右移offset位。以下是这种操作的图形表示

            ╭──────────────────╮
%al old     │ 0 0 1 0'0 1 1 1  │
ror 1, %al  ╰─╮╲ ╲ ╲ ╲ ╲ ╲╰─╯
%al new       1 0 0 1'0 0 1 1

旋转位数offset被掩码到最低 5 位(或在 64 位模式下为 6 位)。这等同于 操作,即整数除法的余数(注意:)。这意味着,你永远无法进行一次或多次“完整”旋转。

操作数

[编辑 | 编辑源代码]
  • Variable 必须是寄存器或内存位置。
  • Offset 可以是
    • 立即值(其中值 1 有一个专门的操作码),
    • 或者 cl 寄存器(即 ecx 的最低字节)。

修改的标志

[编辑 | 编辑源代码]

ror 仅在掩码后的 offset零时才会改变标志。 CF 变成最近旋转的位,因此在 ror 的情况下,resultMSB(“符号”。

此外,如果掩码后的 offset = 1,OF ≔ result[MSB] ⊻ result[MSB−1],因此 OF 告诉我们“符号”是否发生了变化。

rol offset, variable GAS 语法
rol variable, offset Intel 语法


variable左移offset位。

操作数和修改的标志与 ror 相差无几。但是,在掩码后的 offset = 1 的情况下,OF 的定义不同,尽管实际上意义相同。对于 rol 1, xOF ≔ result[MSB] ⊻ result[LSB]。

请注意,在 rol 的情况下,CF 包含 LSB

带进位的旋转指令

[编辑 | 编辑源代码]

类似于移位操作,旋转操作可以使用进位位作为它移位的“额外”位。


rcr cnt, dest GAS 语法
rcr dest, cnt Intel 语法


dest向右旋转cnt位,并使用进位位。


rcl cnt, dest GAS 语法
rcl dest, cnt Intel 语法


dest向左旋转cnt位,并使用进位位。

参数数量

[编辑 | 编辑源代码]

除非另有说明,这些指令可以接受一个或两个参数。如果只提供一个参数,则假定它是寄存器或内存地址,并且要移位/旋转的位数为1(但是,这可能取决于使用的汇编器)。 shrl $1, %eax 等效于 shrl %eax (GAS 语法)。

华夏公益教科书