C语言位操作
1、位操作符
(1)位与& 逻辑与&& 1&1=1 1&0=0 0&0=0 0&1=0
(2)位或 | 逻辑或 || 1 | 1=1 1 | 0=1 0 | 0=0 0 | 1=1
(3)位取反~ 逻辑取反 !
(4)位异或 ^ 不同为1,相同为0 1^1=0 1^0=1 0^0=0 0^1=1
总结:
- 位操作是按照二进制数的每一位进行操作的,逻辑操作是对数的整体操作的
- 位与,与1位与无变化,与0位与为0
- 位或,与1位或为1,与0位或无变化
- 位异或,与1位异或会取反,与0位异或无变化
(5)移位操作
-
C语言的移位要取决于数据类型
-
左移位 << 右移位 >>
-
无符号数, 逻辑移位
左移位时右侧补0
右移位时左侧补0
-
有符号数, 算数移位
左移位时右侧补0(相当于逻辑移位)
右移位时左侧补符号位,如果是正数就补0,负数就补1(第一位为符号位,0为正,1为负)
-
2、位操作符在操作寄存器时的特殊作用
(1)寄存器的操作要求
- ARM是内存和IO统一编址的,CPU通过向内部外设的寄存器写入特定值来操作内部外设
- 寄存器特点:按位进行规划和使用、读写是32位整体进行操作的
- 寄存器的操作要求:操作寄存器时,要求改变某一特定位,而不影响其他位
- 如何实现:读出—>改相应位—>写入
(2)改相应位的操作
基本思路:寄存器特定位改变而不影响其他位,构造合适的1和0组成的数和这个寄存器原来的值位与、位或、位取反
-
特定位清零用 & (用0清零)
-
与1位与无变化、与0位与变为0
-
要清零的位为0,其他位为1,然后这个数与原数位与
-
举例:原32位寄存器的值位0xAAAAAAAA,对bit2~bit8清零
0xAAAAAAAA & 0xFFFFFE03
-
-
特定位置1用 | (用1置1)
-
与1位或为1,与0位或无变化
-
要置1的位为1,其他位为0,然后这个数与原数位或
-
举例:原32位寄存器的值位0xAAAAAAAA,对bit8~bit15置1
0xAAAAAAAA | 0x0000FF00
-
-
特定位取反用^ (用1取反)
-
与1位异或为会取反,与0位异或无变化
-
要取反的位为1,其他位为0,然后这个数与原数位异或
-
举例:原32位寄存器的值位0xAAAAAAAA,对bit8~bit15取反
0xAAAAAAAA ^ 0x0000FF00
-
-
代码举例
unsigned int a = 0xa12aaaa7; unsigned int b = 0xFFFF00FF; unsigned int c; c = a & b; // 16进制数的打印 printf("a & b = %#X.\n", c); printf("a & b = 0x%x.\n", c);
3、使用位运算构建特殊二进制数
(1)使用位移获取特定位为1的二进制数
- 获取bit3-bit7为1,同时bit23-bit25为1,其余都为0的数
- (0x1F<<3) | (0x7<<23)
(2)使用位移获取特定位为0的二进制数
- 获取bit4-bit10为0,其余都为1的数
- bit4-bit10为0也就是bit0-bit3为1同时bit11-bit31为1
(0xF<<0) | (0x1FFFFF<<11) - ~(0x7F<<4)
- bit4-bit10为0也就是bit0-bit3为1同时bit11-bit31为1
(3)总结
- 1少0多,连续多个1左移n位
- 0少1多,先构建其位反数,再按位取反
- 连续1(0)不止一个,则通过分段分别构建,再彼此位与即可
4、位运算的操作实战
回顾:清零用&,置1用 | ,取反用^
~和<< >>用来构建特定二进制数
unsigned int a ; // 定义无符号数,如果是有符号数,那么位移就会出问题
(1)给定一个整型数a,设置a的bit3,保证其他位不变
- a |= (0x1<<3)
(2)给定一个整型数a,设置a的bit3-bit7,保证其他位不变
- a |= (0x1F<<3) a |= (0b11111<<3)
(3)给定一个整型数a,清除a的bit15,保证其他位不变
- a &= (~(0x1<<15)) a = (a | (0x7FFF<<0) | (a | (0xFFFF<<16) (不采用)
(4)给定一个整型数a,清除a的bit15-bit23,保证其他位不变
- a &= (~(0x1FF<<15))
(5)给定一个整型数a,取出a的bit3-bit8
- 先将a的bit3-bit8不变,其余位清零
- a &= (0x3F<<3)
- 再将a右移3位
- a >>= 3
(6)给寄存器的bit7-bit17赋值937,其余位不受影响
- 先将bit7-bit17全部清零,其他位不变
- a &= (~(0x7FF<<7))
- 再将937写入bit7-bit17,其他位不变
- a |= (937<<7)
(7)将寄存器bit7-bit17中的值加17,其余位不受影响
- 先读出bit7-bit17的值
- temp = a & (0x7FF<<7)
- temp >>= 7;
- 给temp加17
- temp += 17
- 将bit7-bit17清零
- a &= (~(0x7FF<<7))
- 将temp算出的值写入到bit7-bit17
- a |= (temp<<7)
(8)给寄存器bit7-bit17赋值937,同时给bit21-bit25赋值17
- 先将bit7-bit17和bit21-bit25清零,其他位不变
- a &= (~ ( (0x7ff<<7) | (0x1f<<21) ) ) // 位或优先级高于~
- 再将937赋值到bit7-bit17,17赋值到bit21-bit25
- a |= ((937<<7) | (17<<21))
printf(“a = %u.\n”, a); // %u 打印无符号数
5、技术升级:使用宏定义来完成运算
(1)直接用宏定义来置位、复位(最右边是第一位,bit0)
// 置位
#define SET_NTH_BIT(x, n) (x | ( (1U) << (n-1) ) )
// 复位
#define CLEAR_NTH_BIT(x, n) (x & ~ ( (1U) << (n-1) ) )
// 1后边的U表示这个数字是无符号数(有符号数的右移是会出问题的)
(2)将32位数x的第n位到第m位置位
// 关键点:我们需要一个算式来得到(m-n+1)个1的十六进制数
// 第1步,先得到32个1: ~0U (~按位取反得到32个1,如果直接1U那么就只有bit0位1)
// 第2步,将得到的数右移x位即可得到(m-n+1)个: (~0U) >> (32-(m-n+1)) 或 ~(~0U<<(m-n+1))
#define SET_BIT_N_M(x, n, m) (x | (((~0U) >> (32-(m-n+1))) << (n-1)))
#define SET_BIT_N_M(x, n, m) (x | ~(~0U<<(m-n+1))<<(n-1))
(3)截取变量的部分连续位(相当于4中的(5))
// 给定一个整型数a,取出a的bit3-bit8
// 先将a的bit3-bit8不变,其余位清零 a &= (0x3F<<3)
// 再将a右移3位 a >>= 3
#define GETBITS(x, n, m) (x & ~(~0U << (m-n+1)) << (n-1)) >> (n-1))
// 思路:
// 先将x的bit(n-1)-bit(m-1)不变,其余位清零
// 得到(m-n+1)个1的十六进制数 ~(~0U << (m-n+1)) (得到0x3F)
// 将得到的16进制数左移(n-1) ~(~0U << (m-n+1)) << (n-1) (得到0x3F<<3)
// x和左移后的数位与 x & ~(~0U << (m-n+1)) << (n-1) (a &= (0x3F<<3))
// 再将x右移(n-1)位
// 位与后的数右移(n-1) (x & ~(~0U << (m-n+1)) << (n-1)) >> (n-1)
// 最终结果
#define GETBITS(x, n, m) (x & ~(~0U << (m-n+1)) << (n-1)) >> (n-1))
// 得到(m-n+1)个1的十六进制数的两种方式:
// 32位的1先左移(m-n+1)位,那么低(m-n+1)位位0,高(32-(m-n+1))位为1,再将其按位取反,
// 就得到低(m-n+1)位为1,高(32-(m-n+1))位为0。
~(~0U << (m-n+1))
// 有(m-n+1)个1,那么就有32-(m-n+1)个0,将32位的1先右移32-(m-n+1),
// 那么高32-(m-n+1)位为0,低(m-n+1)位为1。
(~0U) >> (32-(m-n+1))