为何GCC对64位long型移位计数使用mov %esi, %ecx指令?
为什么GCC在这里用
movl %esi, %ecx处理移位计数? 先回顾下背景:我们有C函数
long shift(long x, long n) { x <<= 4; x >>= n; return x; }
对应的x86-64汇编片段(Linux平台):
# x in %rdi, n in %rsi movq %rdi, %rax # Get x salq $4, %rax # x <<= 4 movl %esi, %ecx # Get n (4 bytes) sarq %cl, %rax # x >>= n
核心原因要从x86-64的移位指令规则和调用约定两个角度拆解:
1. x86-64移位指令的硬性要求
对于64位移位指令(比如这里的sarq,算术右移64位数据),CPU只认两种移位计数来源:
- 一个立即数(比如
salq $4, %rax里的$4) %cl寄存器的低5位(因为64位数据最多只能合法移位63次,5位二进制刚好能覆盖0-63的范围)
换句话说,你直接写sarq %rsi, %rax是行不通的——CPU根本不支持这种指令格式,必须把移位计数放到%cl里才能执行移位操作。
2. 调用约定与代码优化的结合
在x86-64 Linux的函数调用约定里,第二个参数n是存在64位寄存器%rsi中的。那为什么GCC选了movl %esi, %ecx而不是movq %rsi, %rcx?
- 首先,
%esi是%rsi的低32位寄存器,movl指令会把%esi的值写入%ecx的低32位,同时自动将%ecx的高32位清零(这是x86-64的硬件规则:写入32位寄存器时,高32位会被置0)。 - 其次,移位计数只需要
%cl的低5位,哪怕n是64位的long类型,取它的低32位完全足够——高32位的数值对移位操作没有任何意义,CPU会直接忽略。 - 最后,
movl指令的机器码比movq更短(少1个字节),这是GCC做的一个微小但有效的代码优化,让生成的汇编更紧凑高效。
总结下来就是:移位指令必须用%cl当计数寄存器,而GCC选择用更短的32位移动指令把n的有效部分放到%ecx里,刚好满足移位的需求。
内容的提问来源于stack exchange,提问作者user8629136
相关产品推荐
相关产品推荐

