C语言位操作
目录
4)总结:位与、位或结合特定二进制数即可完成寄存器位操作需求
2.给定一个整型数a,设置a的bit3-bit7,保证其他位不变
4.给定一个整型数a,清除a的bit15-bit23,保证其他位不变
6.给寄存器的bit7-bit17赋值937,其余位不受影响
7.将寄存器bit7-bit17中的值加17,其余位不受影响
8.给寄存器bit7-bit17赋值937,同时给bit21-bit25赋值17
2)将32位数x的第n位到第m位置位(从右边起,bit0是第一位,m是高位)
一.位操作符:
1)位与(&)
注:位与符号是一个&,两个&(&&)是逻辑与。
1.位与&的概念:参加运算的两个数据,按二进制位进行“与”运算,通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都是1时,结果才为1。(从真/假方面看,只有两个位都是真的时,结果才为真。)
例:(10010011)&(00111101)上下依次对应。
由于两个运算对象中的编号4和0的位都为1,得结果(00010001)
2.真值表:0&0=0; 0&1=0; 1&0=0; 1&1=1;
3.特点:只有1&1=1,其余全是0。
4.位与和逻辑与的区别:逻辑与是两个操作数作为整体来相与的,而位与是用二进制的。
例:(10010011)&(00111101)=(00010001) 而 (10010011)&&(00111101)=1
2).位或(|)
注:位或的符号是一个|,两个||是逻辑或。
1.位或(|)的概念:参加运算的两个对象,按二进制位进行“或”运算,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为1,结果就为1。(从真/假方面来看,如果两个运算对象中相应的一个位为真或者两个为都为真是,那么结果为真)
例: 00000011 | 0000 0101 = 00000111
2.真值表:0|0=0; 0|1=1; 1|0=1; 1|1=1;
3.特点:参加运算的两个对象只要有一个为1,其值为1。
4.位或和逻辑或的区别:位或是依次对应,逻辑或是两个操作数作为整体来相或的。
3)位取反(~)
注:位取反为(~),而逻辑取反是(!)
1.按位取反的概念:按位取反是将操作数的二进制位逐个按位取反(1变成0,0变成1)
例:~(10011010)=(01100101)
2.位取反和逻辑取反的区别:逻辑取反是(真变成假,假变成真)(在c语言中只要不是0的任何数都是真;只有0表示假);位取反是(1变成0,0变成1)
3.若是按位取反再按位取反时那么得到的时原来的数;若是逻辑取反后再逻辑取反那么得到的不是原数,只能是1或者0.
例:
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);
4)位异或(^)
1.位异或(^)的概念:参加运算的两个数据,按二进制位进行“异或”运算,对于每个运算对象中相应的位一个为1(但不是为1),结果为1.(从真/假方面来看,如果两个对象中相应的一个位为真且不是两个为同时为1,那么接果为真)。
例:(10010011)^(00111101)
2.真正表:0^0=0; 0^1=1; 1^0=1; 1^1=0;
3.特点:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
异或的几条性质:
1、交换律
2、结合律(即(a^b)^c == a^(b^c))
3、对于任何数x,都有x^x=0,x^0=x
4、自反性: a^b^b=a^0=a;
5)左移位(<<)右移位(>>)
1.c语言的移位取决于数据类型:
对于无符号数,左移时右侧补0(相当于逻辑移位)
例:(10001010)<<2 =(00101000)左侧运算对象移出左端末端位的值丢失。
对于无符号数,右移时左侧补0(相当于逻辑移位)
右移同左移
对于有符号数,左移时右侧补0(叫算数移位,相当于逻辑移位)
对于有符号数,右移时左侧补符号位(正数补0,负数补1,叫算数移位)
但嵌入式研究的移位,以及使用的移位都是无符号的。
二:位于位或位异或在操作寄存器时的特殊作用
1).寄存器操作的要求(特定位改变而不影响其他位):
1.ARM是内存与IO统一编值址的,ARM中有很多内部外设,SoC中CPU通过向这些内部外设的寄存器写入一些特定的值来操控这个内部外设,进而操控硬件动作。所以可以说:读写寄存器就是操控硬件。
2.寄存器的特点是按位进行规划和使用的,但寄存器的读写确实整体32位一起的(就是说想修改bit5~bit7是不可以的,必须整体32bit全部写入)
3.寄存器的操作要求就是:在设定特定位时不能影响其他位。
4.那么我们如何能做到以上的步骤呢?:
读——改——写:意思是当我想改变一个寄存器中某些特定位时,不会直接去改写,而是先读出寄存器原来的值,然后在这个基础上修改我想要修改的特定位,再将修改后的值整体写入寄存器。这样的效果是:在不影响位原来值的情况下,我想要改的值可以被修改。
2)特定位清零用&
1.位与操作的特点:(任何数,其实就是1或者0)与1位与无变化,与0位与变成0
2.如果希望将一个寄存器的某些特定位变成0而不影响其他位,可以构造一个合适的1和0组成的数和这个寄存器原来的值进行位与操作,就可以将特定位清零。
3.举例:假设原来32位寄存器中的值为:0xAAAAAAAA,我们希望将bit8~bit15清零而其他位不变,可以将这个数与0xFFFF00FF进行位与即可。
3)特定位置1用 |
1.位或操作的特点:任何数,其实就是1或者0)与1位或变成1,与0位或无变化
2.操作手法和刚才讲的位与是类似的。我们要构造这样一个数:要置1的特定位为1,其他位为0,然后将这个数与原来的数进行位或即可。
4)特定位取反用^
1.位异或操作的特点:(任何数,其实就是1或者0)与1位异或会取反,与0位异或无变化
2.操作手法和刚才讲的位与是类似的。我们要构造这样一个数:要取反的特定位为1,其他位为0,然后将这个数与原来的数进行位异或即可。
三.如何运用位运算构建特定的二进制数
1)寄存器位操作经常需要特定位给特定值
(1)从上节可知,对寄存器特定位进行置1或者清0或者取反,关键性的难点在于要事先构建一个特别的数,这个数和原来的值进行位与、位或、位异或等操作,即可达到我们对寄存器操作的要求。
(2)解法1:用工具软件或者计算器或者自己大脑计算,直接给出完整的32位特定数。
优势:可以完成工作,难度也不大,操作起来也不是太麻烦。
劣势:依赖工具,而且不直观,读程序的人不容易理解。
评价:凑活能用,但是不好用,应该被更好用的方法替代。
解法2:自己写代码用位操作符号(主要是移位和位取反)来构建这个特定的二进制数
2)使用移位获取特定位为1的二进制数
(1)最简单的就是用移位来获取一个特定位为1的二进制数。譬如我们需要一个bit3~bit7为1(隐含意思就是其他位全部为0)的二进制数,可以这样:(0x1f<<3)
(2)更难一点的要求:获取bit3~bit7为1,同时bit23~bit25为1,其余位为0的数:((0x1f<<3) | (7<<23))
3)再结合位取反获取特定位为0的二进制数
1.这次我们要获取bit4~bit10为0,其余位全部为1的数。怎么做?
利用上面讲的方法就可以:(0xf<<4)|(0x1fffff<<11)
但是问题是:连续为1的位数太多了,这个数字本身就很难构造,所以这种方法的优势损失掉了。
2.这种特定位(比较少)为0而其余位(大部分)为1的数,不适合用很多个连续1左移的方式来构造,适合左移加位取反的方式来构造。
3.思路是:先试图构造出这个数的位相反数,再取反得到这个数。(譬如本例中要构造的数bit4~bit10为0其余位为1,那我们就先构造一个bit4~bit10为1,其余位为0的数,然后对这个数按位取反即可)
4)总结:位与、位或结合特定二进制数即可完成寄存器位操作需求
1.如果你要的这个数比较少位为1,大部分位为0,则可以通过连续很多个1左移n位得到。
2.如果你想要的数是比较少位为0,大部分位为1,则可以通过先构建其位反数,然后再位取反来得到。
3.如果你想要的数中连续1(连续0)的部分不止1个,那么可以通过多段分别构造,然后再彼此位与即可。这时候因为参与位或运算的各个数为1的位是不重复的,所以这时候的位或其实相当于几个数的叠加。
四:位运算实战演练(设置为1,清除为0)
unsigned int a ; // 定义无符号数,如果是有符号数,那么位移就会出问题
注:要置1,用|;要清零,用&;要取反,用^;~和<<,>>来构建特定的二进制数
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)
这是定义32位数x的第n位。
置位:(就是置1)
#define SET_NTH_BIT(x, n) (x | ( (1U) << (n-1) ) ){这个U是代表无符号的意思,但很多时候也是可加可不加}
复位:(就是清0)
#define CLEAR_NTH_BIT(x, n) (x & ~ ( (1U) << (n-1) ) )
注:1后边的U表示这个数字是无符号数(有符号数的右移是会出问题的);n如果是1的话是bit0,n如果是2的话是bit1。
2)将32位数x的第n位到第m位置位(从右边起,bit0是第一位,m是高位)
第1步,先得到32个1: ~0U (~按位取反得到32个1,如果直接1U那么就只有bit0位1)
第2步,将得到的数右移x位即可得到(m-n+1)个: (~0U) >> (32-(m-n+1)) 或 ~(~0U<<(m-n+1))
1.#define SET_BIT_N_M(x, n, m) (x | (((~0U) >> (32-(m-n+1))) << (n-1)))
2.#define SET_BIT_N_M(x, n, m) (x | ~(~0U<<(m-n+1))<<(n-1))
3)截取变量的部分连续位
给定一个整型数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))
思路:
1.先将x的bit(n-1)-bit(m-1)不变,其余位清零
2.得到(m-n+1)个1的十六进制数 ~(~0U << (m-n+1)) (得到0x3F)
3.将得到的16进制数左移(n-1) ~(~0U << (m-n+1)) << (n-1) (得到0x3F<<3)
4. x和左移后的数位与 x & ~(~0U << (m-n+1)) << (n-1) (a &= (0x3F<<3))
5.再将x右移(n-1)位
6.位与后的数右移(n-1) (x & ~(~0U << (m-n+1)) << (n-1)) >> (n-1)
7.最终结果:
#define GETBITS(x, n, m) (x & ~(~0U << (m-n+1)) << (n-1)) >> (n-1))
总结:运算时位运算和逻辑语要分清楚。宏定义要看到知道是干什么的。再复杂的位运算也是基于普通的运算符来运算的。有一个特点非常重要,要牢记:要置1,用|;要清零,用&;要取反,用^;~和<<,>>来构建特定的二进制数。
问题:在宏定义的截取变量的部分连续时有的运算优先级没有想明白以及有一步不明白为什么是(n-1)。