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
的情况下,result 的 MSB(“符号”。
此外,如果掩码后的 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, x
,OF ≔ 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 语法)。
- ↑ 英特尔® 64 和 IA-32 架构软件开发人员手册 第 2 卷 (PDF, 6.2 MB)