运算符 | 名称 | 描述 | 示例 | 结果(基于示例) |
---|---|---|---|---|
& |
按位与 | 两个位都为1时,结果才为1 | 5 & 3 |
1 (0b0001 ) |
` | ` | 按位或 | 两个位中只要有一个为1,结果就为1 | `5 |
^ |
按位异或 | 两个位相同时为0,不同时为1 | 5 ^ 3 |
6 (0b0110 ) |
~ |
按位取反 | 将操作数的每一位取反(0变1,1变0) | ~5 |
-6 (依赖位数) |
<< |
左移 | 将操作数的二进制位向左移动指定的位数,右边空位补0 | 5 << 1 |
10 (0b1010 ) |
>> |
右移 | 将操作数的二进制位向右移动指定的位数,左边空位补符号位(算术右移)或0(逻辑右移) | 5 >> 1 |
2 (0b0010 ) |
🧮 位运算详解与示例
1. 按位与(&)
逐位比较,只有两个对应的位都为1时,结果位才为1。常用于屏蔽(清零)特定位 或检查特定位是否为1。
c
#include <stdio.h>
int main() {
unsigned int a = 0b11001100; // 204
unsigned int b = 0b10101010; // 170
unsigned int result = a & b; // 0b10001000 (136)
printf("a & b = 0x%02X\n", result); // 输出: a & b = 0x88
return 0;
}
2. 按位或(|)
逐位比较,只要两个对应的位中有一个为1,结果位就为1。常用于将特定位设置为1。
c
#include <stdio.h>
int main() {
unsigned int a = 0b11001100; // 204
unsigned int b = 0b10101010; // 170
unsigned int result = a | b; // 0b11101110 (238)
printf("a | b = 0x%02X\n", result); // 输出: a | b = 0xEE
return 0;
}
3. 按位异或(^)
逐位比较,如果两个对应的位不同,则结果位为1;相同则为0。常用于翻转特定位 或交换两个变量的值而不使用临时变量。
c
#include <stdio.h>
int main() {
unsigned int a = 0b11001100; // 204
unsigned int b = 0b10101010; // 170
unsigned int result = a ^ b; // 0b01100110 (102)
printf("a ^ b = 0x%02X\n", result); // 输出: a ^ b = 0x66
// 交换两个变量
a = a ^ b;
b = a ^ b; // 等价于 (a ^ b) ^ b = a
a = a ^ b; // 等价于 (a ^ b) ^ a = b (此时b已经是原来的a)
printf("After swap: a = %u, b = %u\n", a, b);
return 0;
}
4. 按位取反(~)
将操作数的每一位取反(0变成1,1变成0)。这是一个单目运算符。
c
#include <stdio.h>
int main() {
unsigned int a = 0b00001111; // 15
unsigned int result = ~a; // 0b11110000 (240) (假设8位)
// 但实际中unsigned int是32位或64位,结果会很大
printf("~a (unsigned) = 0x%X\n", result);
int b = 15; // 有符号整数
int signed_result = ~b; // 所有位取反,包括符号位
printf("~b (signed) = %d\n", signed_result); // 输出: -16
return 0;
}
注意:对无符号整数和有符号整数进行取反操作,其结果的理解有所不同。
5. 左移(<<)
将操作数的二进制位整体向左移动指定的位数,右边空出的位用0填充。左移一位相当于乘以2(在没有溢出的情况下)。
c
#include <stdio.h>
int main() {
unsigned int a = 0b00001111; // 15 (二进制 ...00001111)
unsigned int result = a << 2; // 0b00111100 (60) (...00111100)
printf("a << 2 = %u\n", result); // 输出: 60
int b = 1;
printf("1 << 31 (on 32-bit system): %d\n", 1 << 31); // 可能产生负数或溢出
return 0;
}
注意 :左移操作需要小心溢出 和符号位问题。
6. 右移(>>)
将操作数的二进制位整体向右移动指定的位数。右移一位相当于除以2(对于非负整数,且向下取整)。
- 逻辑右移 :对于无符号整数,左边空出的位用0填充。
- 算术右移 :对于有符号整数,左边空出的位通常用符号位填充(即保持符号不变)。
c
#include <stdio.h>
int main() {
unsigned int u_num = 0b10001100; // 140 (无符号)
u_num = u_num >> 2; // 逻辑右移: 0b00100011 (35)
printf("u_num >> 2 (unsigned) = %u\n", u_num);
int s_num = -80; // 有符号负数 (其二进制表示是补码形式)
s_num = s_num >> 2; // 算术右移: 保持符号位,结果仍是负数
printf("s_num >> 2 (signed) = %d\n", s_num); // 输出: -20
return 0;
}
⚙️ 位运算在寄存器操作中的应用(逆向工程重点)
在嵌入式系统和驱动开发中,经常需要直接操作硬件寄存器。这些寄存器中的每一个位或几个位可能控制着特定的功能。位运算,尤其是按位与(&)、按位或(|) 结合移位操作(<<, >>),是实现这种精确控制的利器。
1. 寄存器位操作常用技巧
假设我们有一个32位的控制寄存器 *reg
。
-
置位(Set a Bit) :将特定位置1,而不影响其他位。
c*reg |= (1 << 3); // 将第3位置1 (从0开始计数)
-
清零(Clear a Bit) :将特定位清0,而不影响其他位。
c*reg &= ~(1 << 5); // 将第5位清0
-
** toggle位(Toggle a Bit)**:将特定位翻转(1变0,0变1)。
c*reg ^= (1 << 7); // 翻转第7位
-
检查位(Check a Bit) :检查特定位是否为1。
cif (*reg & (1 << 2)) { // 检查第2位是否为1 // 该位为1 }
2. 实例:模拟LED控制
假设一个LED由某个GPIO端口寄存器的第5位控制(1点亮,0熄灭)。
c
#include <stdio.h>
#include <unistd.h> // 用于sleep函数
// 假设的寄存器地址和映射(实际中根据芯片手册定义)
#define GPIO_OUT_REG (*(volatile unsigned int *)0x40010000)
void turn_on_led() {
GPIO_OUT_REG |= (1 << 5); // 第5位置1,点亮LED
}
void turn_off_led() {
GPIO_OUT_REG &= ~(1 << 5); // 第5位清0,熄灭LED
}
int main() {
turn_on_led();
printf("LED is ON\n");
sleep(1); // 延迟1秒
turn_off_led();
printf("LED is OFF\n");
return 0;
}
说明:
volatile
关键字告诉编译器该变量的值可能会被硬件或其他线程意外改变,禁止编译器对其进行优化,确保每次访问都从内存读取或写入。- 实际寄存器地址和位定义需参考具体的芯片数据手册。
🧠 位运算符的优先级
位运算符的优先级有时会带来意想不到的结果。以下是从高到低的优先级顺序(同一行优先级相同):
优先级 | 运算符 | 结合性 |
---|---|---|
最高 | ~ (按位取反) |
右到左 |
<< , >> |
左到右 | |
& (按位与) |
左到右 | |
^ (按位异或) |
左到右 | |
最低 | ` | ` (按位或) |
强烈建议 :在复杂的表达式中大量使用括号 ()
来明确指定运算顺序,避免依赖记忆优先级而引入错误。
c
// 一个可能令人困惑的例子
int value = 0xFF;
int result = ~value & 0xF; // 等价于 (~value) & 0xF,而不是 ~(value & 0xF)
// 清晰的写法是:
int clear_result = (~value) & 0xF;
💡 位运算的实用技巧
位运算的魅力在于其高效和巧妙。
-
判断奇偶性
cif (x & 1) { // x是奇数 } else { // x是偶数 }
-
交换两个变量的值(不借助临时变量)
ca = a ^ b; b = a ^ b; // b now = original a a = a ^ b; // a now = original b
-
检查2的幂次
cif ((x & (x - 1)) == 0) { // x是2的幂(且x不为0) }
-
提取RGB颜色分量
cunsigned int color = 0x00FF00; // 绿色 unsigned char red = (color >> 16) & 0xFF; // 红色分量 0x00 unsigned char green = (color >> 8) & 0xFF; // 绿色分量 0xFF unsigned char blue = color & 0xFF; // 蓝色分量 0x00
-
位掩码(Bit Mask)
使用位掩码可以高效地管理多个布尔状态或选项。
c#define OPTION_A (1 << 0) // 0b00000001 #define OPTION_B (1 << 1) // 0b00000010 #define OPTION_C (1 << 2) // 0b00000100 unsigned char flags = 0; flags |= OPTION_A | OPTION_C; // 设置选项A和C if (flags & OPTION_B) { ... } // 检查选项B是否设置 flags &= ~OPTION_A; // 清除选项A