2. 移位操作
移位操作,说白了就是让二进制位在寄存器里「搬家」。这玩意儿在嵌入式开发里太常用了——协议解析、状态机设计、位图操作,哪哪都离不开它。我刚开始学的时候觉得不就是左移右移嘛,有啥好讲的?结果踩了几个坑才发现,这里面的门道还真不少。
左移操作:最简单的乘法
左移就是把所有位往左边挪,右边补0。每左移一位,数值就翻一倍。嗯,前提是不溢出。
uint8_t a = 0x05; // 二进制 0000 0101
uint8_t b = a << 2; // 结果 0001 0100 = 0x14 = 20
你看,5左移2位变成了20,正好是5×4。我在做PWM占空比计算时经常这么干——用左移代替乘法,比调用乘法指令快多了。不过要注意,左移可能会把高位数据挤出去,这就叫溢出。
右移:算术移位 vs 逻辑移位
右移就复杂了。这里有个大坑——C语言标准没有规定右移是有符号还是无符号的。说白了,编译器说了算。
逻辑右移:左边补0,适用于无符号数。
uint8_t a = 0xF0; // 1111 0000
uint8_t b = a >> 2; // 0011 1100 = 0x3C
算术右移:左边补符号位,适用于有符号数。
int8_t a = -16; // 1111 0000
int8_t b = a >> 2; // 1111 1100 = -4(算术右移)
为什么会这样?因为算术右移要保持负数的符号不变。你想想看,-16除以4应该是-4,如果左边补0就变成正数了,那不乱套了?
我的经验: 我个人习惯在嵌入式代码里,对无符号数用逻辑右移,对有符号数用算术右移。但为了保险,我建议你写代码时明确告诉编译器你的意图——用 unsigned 类型做逻辑移位,用 signed 类型做算术移位。
循环移位:位运算里的「旋转木马」
循环移位和普通移位不一样。普通移位移出去的位就丢了,循环移位会把移出去的位补到另一边。就像旋转木马,转一圈又回来了。
C语言没有直接支持循环移位的运算符,得自己写。我在做CRC校验时经常用到这个。
// 循环左移
uint8_t rotl8(uint8_t value, uint8_t shift) {
return (value << shift) | (value >> (8 - shift));
}
// 循环右移
uint8_t rotr8(uint8_t value, uint8_t shift) {
return (value >> shift) | (value << (8 - shift));
}
举个例子,0x81循环左移1位:
0x81 = 1000 0001
循环左移1位 = 0000 0011 = 0x03
你看,最高位的1跑到了最低位。这在加密算法里特别有用,比如AES的列混合运算就用到了循环移位。
💡 小技巧: 我曾经在调试一个SPI协议解析时,发现数据总是对不上。查了半天,原来是接收到的数据需要循环移位才能对齐位序。从那以后,我写协议驱动时都会先确认一下数据的位序是大端还是小端。
四种移位操作对比
| 操作类型 | 补位方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| 左移 | 右边补0 | 快速乘法、位掩码生成 | 注意溢出,移位位数不能超过类型宽度 |
| 逻辑右移 | 左边补0 | 无符号数除法、位提取 | 对有符号数使用可能产生意外结果 |
| 算术右移 | 左边补符号位 | 有符号数除法、保持符号 | 不同编译器行为可能不同 |
| 循环移位 | 移出位补到另一端 | 加密算法、CRC、位序调整 | C语言无原生支持,需手动实现 |
避坑指南
我曾经在写一个I2C驱动时,用右移来提取数据中的某个位。结果在ARM和x86上跑出来的结果不一样。查了半天,原来是ARM的右移默认是算术移位,而x86是逻辑移位。从那以后,我写移位操作时都会加上类型转换,明确告诉编译器我要什么。
⚠ 重要提醒:
- 移位位数不能大于等于类型宽度。比如uint8_t最多移7位,移8位是未定义行为。
- 有符号数的右移是算术移位还是逻辑移位?C标准说「由实现定义」。说白了,别依赖它。
- 循环移位在C语言里没有运算符,别想着用 <<< 或 >>>,那是某些汇编才有的。
知识体系总览
下面这张图把移位操作的整个知识结构串起来了。我画图时习惯把核心概念放在中间,然后往外发散。你看,左移和右移是基础,右移又分算术和逻辑两种,循环移位则是更高级的玩法。
这张图把四种移位操作的关系理清楚了。左移和右移是基础,右移又根据补位方式不同分为逻辑和算术两种。循环移位则是在这个基础上加了「循环」的特性。你写代码时,先想清楚你要的是哪种,再动手。
核心要点:
- 左移 = 乘2,注意溢出
- 右移分两种:无符号用逻辑,有符号用算术
- 循环移位需要手动实现,别偷懒
- 移位位数不能超过类型宽度,这是硬性规定
好了,移位操作就讲这么多。记住一点:写代码时多想想「这个移位会带来什么后果」,尤其是跨平台的时候。我吃过这个亏,希望你不用再吃一次。
可可精品资料库