一句话定位
EIP-145 是给以太坊虚拟机补数学课的:在它之前,EVM 有加减乘除、有按位与或非,唯独没有位移。你想把一个数左移三位,只能乘八;右移三位,只能除八。指令本身能做对,但代价偏高,而且除法和乘法在合约字节码里比一条位移指令笨重得多。EIP-145 在 2019 年的君士坦丁升级中随主网激活,给 EVM 加了三条指令:SHL(左移)、SHR(逻辑右移)、SAR(算术右移)。

三条指令各管什么
三条指令的输入都一样:第一个操作数是移位的位数,第二个是被移的数。注意顺序——移位的位数在前,被移的数在后,这一点和 Solidity 源码里的 x << n 写法正好相反,直接手写汇编时容易搞混。
SHL 是左移:把 256 位宽的数整体向左推,右边空出来的位补零,左边挤出去的位直接丢弃。左移 N 位,效果约等于乘二的 N 次方,但一旦溢出 256 位,超出部分静默消失,不会报错。
SHR 是逻辑右移:整体向右推,左边空出来的位一律补零。无符号数右移 N 位约等于除以二的 N 次方并向下取整。EVM 的数本来就是 256 位无符号整数,所以大多数场景下 SHR 是默认选择。
SAR 是算术右移:和 SHR 的区别只在补位规则——它看原数的最高位(符号位),最高位是 1 就补 1,是 0 就补 0。这条指令是为「有符号数」准备的。如果你把一个负数按补码形式装进 256 位槽,再用 SHR 右移,负号会被移没,结果变成一个天文数字;SAR 则能把符号位一路复制下去,保持数值的符号不变。
为什么非要有专门的指令
第一位的原因是钱。在 EIP-145 之前,位移靠乘除法实现,收的是乘除法的 Gas;而位移指令在规范里的定价与加法同档,比乘除法便宜。合约里位移用得很多,差价累积起来不是小数。
第二位的原因是正确性。乘八确实等价于左移三位,但除法并不完全等价于右移:除以八和右移三位在边界情形上偶有出入,而且开发者很容易在应该用 SAR 的地方写成除法或 SHR,导致有符号逻辑悄悄错位。有了原生指令,编译器可以直接发出语义准确的字节码,不需要发明微妙的取巧写法。
位移在合约里的真实用武之地
单个大整数很少需要位移,位移的主战场是「打包」:把几个小于 256 位的小数挤进同一个存储槽。比如把一个 128 位的计数器和一个 128 位的时间戳拼在一个槽里省 Gas——存入时把时间戳左移 128 位再和计数器做按位或;读出时用 SHR 128 把高位捞回来,用掩码与运算留下低位。N 个 32 位的小整数打包进一个槽,靠的就是依次移位再相或。
另一个常见场景是位图(bitmap):用一个 256 位数的每一位代表一个编号「开没开」,置位用 SHL 配合按位或,检查用 SHR 后取末位。投票、认领、去重都在这么省空间。
快速问答
问:SAR 和 SHR 平时用哪个? 答:只要你的数是无符号的(Solidity 里绝大多数 uint 变量都是),用 SHR。只有当你手动把有符号数编码进无符号槽时才需要 SAR,而且 Solidity 有符号类型 right shift 语义近年还有过调整,手写汇编前建议查当期编译器文档,不要凭旧经验。
问:移位的位数超过 255 会怎样? 答:结果为全零(SAR 时按符号位补成全零或全一),不会报错。移位位数是一个操作数,不是编译期常量,所以这条规则在运行时生效。
一条直觉线
可以把 256 位存储槽想象成一条横向的公寓走廊:每个小整数住一格或几格,住哪一格由移位量决定。SHL 是搬家往左走,SHR 是往右走,掩码与运算是把不需要的房间清场。打包-解包的全部纪律就是「入住位置写进文档」:同一个槽,编码时左移九十六位、解码时右移九十六位,两个数字对不上,读出来就是别人家的门牌号。链上数据不可改,这类约定一旦部署就永久生效——所以位移方案值得像设计数据库表结构一样先画图纸。
风险提示
本文是协议机制科普,不构成任何投资建议。位移逻辑写错可能让合约的钱算错账,涉及资金的代码请走专业审计,并核对你使用的 Solidity 版本中移位与取整的实际语义。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。