1.位操作符
1.1位與&
(1)注意:位與符號(hào)是一個(gè)&,兩個(gè)&&是邏輯與。
(2)真值表:1&0=0 1&1=1 0&0=0 0&1=0
(3)從真值表可以看出:位與操作的特點(diǎn)是:只有1和1位與結(jié)果為1,其余全是0。
(4)位與和邏輯與的區(qū)別:位與時(shí)兩個(gè)操作數(shù)是按照二進(jìn)制位彼此對(duì)應(yīng)位相與的,邏輯與是兩個(gè)操作數(shù)作為整體來(lái)相與的。(舉例:0xAA&0xF0=0xA0 0xAA&&0xF0=1)
1.2位或|
(1)注意:位或符號(hào)是一個(gè)|,兩個(gè)||是邏輯或。
(2)真值表:1|0=1 1|1=1 0|0=0 0|1=1
(3)從真值表可以看出:位或操作的特點(diǎn)是:只有兩個(gè)0相位或才能得到0,只要有一個(gè)1,結(jié)果就為1。
(4)位或和邏輯或的區(qū)別:位或時(shí)兩個(gè)操作數(shù)是按照二進(jìn)制彼此對(duì)應(yīng)位相或的,邏輯或是兩個(gè)操作數(shù)作為整體來(lái)相或的。
1.3位取反~
(1)注意:C語(yǔ)言中位取反是~,C語(yǔ)言中的邏輯取反是!
(2)安慰取反試講操作數(shù)的二進(jìn)制位逐個(gè)按位取反(1變成0,0變成1);而邏輯取反是真(在C語(yǔ)言中只要不是0的任何數(shù)都是真)變成假(在C語(yǔ)言中只有0表示假)、假變成真。
結(jié)論:
任何非0的數(shù)被按邏輯取反再取反就會(huì)得到1;
任何非0的數(shù)被按位取反再取反就會(huì)得到它自己。
1.4位異或^
(1)位異或真值表:1^1=0、0^0=0、1^0=1、0^1=1
(2)位異或的特點(diǎn):2個(gè)數(shù)如果相等結(jié)果為0,不等結(jié)果為1。記憶方法:異或就是相異就或操作起來(lái)。
位與、位或、位異或的特點(diǎn)總結(jié):
位與:(任何數(shù),其實(shí)就是1或者0)與1位與無(wú)變化,與0位與變成0。
位或:(任何數(shù),其實(shí)就是1或者0)與1位或變成1,與0位或無(wú)變化。
位異或:(任何數(shù),其實(shí)就是1或者0)與1位異或會(huì)取反,與0位異或無(wú)變化。
1.5左移<<、右移>>
C語(yǔ)言的移位要取決于數(shù)據(jù)類(lèi)型
對(duì)于無(wú)符號(hào)數(shù),左移時(shí)右側(cè)補(bǔ)0(相當(dāng)于邏輯移位)
對(duì)于無(wú)符號(hào)數(shù),右移時(shí)左側(cè)補(bǔ)0(相當(dāng)于邏輯移位)
對(duì)于有符號(hào)數(shù),左移時(shí)右側(cè)補(bǔ)0(叫算術(shù)移位,相當(dāng)于邏輯移位)
對(duì)于有符號(hào)數(shù),右移時(shí)左側(cè)補(bǔ)補(bǔ)符號(hào)位(如果正數(shù)就補(bǔ)0,負(fù)數(shù)就補(bǔ)1,叫算術(shù)移位)
嵌入式中大多數(shù)研究的移位都是無(wú)符號(hào)數(shù)。
2.位與位或位異或在操作寄存器時(shí)的特殊作用
2.1寄存器操作的需求(特定位改變不影響其他位)
(1)ARM是內(nèi)存與IO統(tǒng)一編址的,ARM中有很多內(nèi)部外設(shè),Soc中CPU通過(guò)這些內(nèi)部外設(shè)的寄存器寫(xiě)入一些特定的值來(lái)操控這個(gè)內(nèi)部外設(shè),進(jìn)而操控硬件動(dòng)作。所以可以說(shuō):讀寫(xiě)寄存器就是操控硬件。
(2)寄存器的特點(diǎn)是按位進(jìn)行規(guī)劃和使用。但是寄存器的讀寫(xiě)確實(shí)是整體32位一起進(jìn)行的(也就是說(shuō)你只想修改bit5—bit7是不行的,必須整體32位全部寫(xiě)入)。
(3)如何做到?答案是:讀、改、寫(xiě)三部曲。讀改寫(xiě)的操作理念,就是:當(dāng)我想改變一個(gè)寄存區(qū)中某些特定位時(shí),我不會(huì)直接去給他寫(xiě),我會(huì)先讀出寄存器整體原來(lái)的值,然后在這個(gè)基礎(chǔ)上修改我想要修改的特定位,再將修改后的值整體寫(xiě)入寄存器。這樣達(dá)到的效果是:在不影響其他原來(lái)值的情況下,我關(guān)心的位的值已經(jīng)被修改了。
2.2特定位清零用&
(1)位與的操作特點(diǎn):任何數(shù),其實(shí)就是1或者0)與1位與無(wú)變化,與0位與變成0。
(2)如果希望講一個(gè)寄存器的某些特定位變成0而不影響其他位,可以構(gòu)造一個(gè)合適的1和0組成的數(shù)和這個(gè)寄存器原來(lái)的值進(jìn)行位與操作,就可以將特定位清零。
(3)舉例:①假設(shè)原來(lái)32位寄存器中的值為0xAAAAAAAA,我們希望將bit8—bit15清零而其他位不變,可以將這個(gè)數(shù)0xFFFF00FF進(jìn)行位與即可。
int main(void)
{
unsigned int a = 0x12aaaaa7;
unsigned int b = 0xFFFF00FF;
unsigned int c;
c = a & b;
printf("a&b=0x%x.\n", c); //12aa00a7
}
②把一個(gè)寄存器值的bit13—bit21清0,其他位不變。使用寄存器查看器https://pan.baidu.com/s/1Ija0klYH1JgqADiOLhpLxQ
int main(void)
{
unsigned int a = 0x123dc57;
unsigned int b = 0xffc01fff; //這個(gè)值是朱老師開(kāi)發(fā)的寄存器查看器去操作得到的。
unsigned int c;
c = a & b;
printf("a&b=0x%x.\n", c); //12000c57
}
2.3特定位置1用|
(1)位或操作的特點(diǎn):(任何數(shù),其實(shí)就是1或者0)與1位或變成1,與0位或無(wú)變化。
(2)操作方法與上一節(jié)相類(lèi)似,我們要構(gòu)造一個(gè)這樣的數(shù):要置1的特定位為1,其他位為0,然后將這個(gè)數(shù)與原來(lái)的數(shù)進(jìn)行位或操作即可。
舉例:擺個(gè)寄存器值的bit4—bit7置1,其他位不變
int main(void)
{
unsigned int a = 0x123dc57;
unsigned int b = 0xf0; //因?yàn)閒左邊都為0所以可以省略
unsigned int c;
c = a | b;
printf("a|b=0x%x.\n", c); //123d0cf7
}
2.4特定位取反用^
(1)位異或操作的特點(diǎn):(任何數(shù),其實(shí)就是1或者0)與1位異或會(huì)取反,與0位異或無(wú)變化。
(2)操作手法和上一節(jié)講的位與類(lèi)似。我們要構(gòu)造這樣一個(gè)數(shù):要取反的特定位為1,其他位為0,然后將這個(gè)數(shù)與原來(lái)的數(shù)進(jìn)行位異或操作即可。
舉例:把一個(gè)寄存器值的bit4—bit7取反,其他位不變。
int main(void)
{
unsigned int a = 0x123dc57;
unsigned int b = 0xf0; //因?yàn)閒左邊都為0所以可以省略
unsigned int c;
c = a ^ b;
printf("a^b=0x%x.\n", c);
}
3.如何用位運(yùn)算構(gòu)建特定二進(jìn)制數(shù)
3.1寄存器位操作經(jīng)常需要特定位給特定值
(1)對(duì)寄存器位操作經(jīng)常需要特定位給特定值或者取反,關(guān)鍵性的難點(diǎn)在于要事先構(gòu)建一個(gè)特別的數(shù),這個(gè)數(shù)和原來(lái)的值進(jìn)行位與、位或、位異或操作,即可達(dá)到我們對(duì)寄存器操作的要求。
(2)解法1:用工具如阿健或者計(jì)算器或者大腦計(jì)算,直接給出完整的32位特定數(shù),比較難。
優(yōu)勢(shì):可以完成工作,難度也不大,操作起來(lái)也不是太麻煩。
劣勢(shì):依賴(lài)工具,而且不是很直觀,讀程序的人不容易理解。
評(píng)價(jià):湊合能用,但是不好用,應(yīng)該被更好的方法代替。
(3)解法2:自己寫(xiě)代碼用位操作符號(hào)(主要是移位和位取反)來(lái)構(gòu)建這個(gè)特定的二進(jìn)制數(shù)。
3.2使用移位獲取特定位為1的二進(jìn)制數(shù)
(1)最簡(jiǎn)單的就是用移位來(lái)獲取一個(gè)特定位為1的二進(jìn)制數(shù)。比如我們需要一個(gè)bit3~bit7為1(隱含的意思就是其他位全部為0)的二進(jìn)制數(shù),可以這樣:(0
x1f<<3)。 0x1f代表11111,在16進(jìn)制數(shù)來(lái)看,后四位為一個(gè)f,加上第五位1,就是0x1f。
(2)更難一點(diǎn)的要求:獲取bit3~bit7為1,bit23~bit25為1,其余位為0的數(shù):((0x1f<<3) | (7<<23)) 這里的7代表111
int main(void)
{
unsigned int a;
a = ((0x1f<<3) | (7<<23));
printf("a=0x%x.\n", a); // 0x38000f8
return 0;
}
((0x1f<<3) | (7<<23))移位解析:
位或說(shuō)明這個(gè)數(shù)字由2部分組成,第一部分中左移3位說(shuō)明第一部分從bit3開(kāi)始,第一部分?jǐn)?shù)字為0x1f說(shuō)明這部分有5位,所以第一部分其實(shí)就是bit3~bit7;第二部分的解讀方法是同樣的,數(shù)字0x7說(shuō)明這部分有3位,第二部分就是bit23到bit25;所以這兩部分結(jié)合起來(lái)就是bit3~bit7和bit23~bit25為1,其余位全部為0。
3.3再結(jié)合位取反獲取特定位為0的二進(jìn)制數(shù)
(1)這次我們要獲取bit4~bit10為0,其余位全部為1的數(shù)。怎么做?
(2)利用上面講的方法就可以得到:(0xf<<0) | (0x1fffff<<11) 這個(gè)意思就是03和1131為1,這樣取。但是問(wèn)題在于,連續(xù)為1的數(shù)太多了,這個(gè)數(shù)本身就很難構(gòu)造,所以這種方法的優(yōu)勢(shì)就沒(méi)了。
(3)這種特定位(比較少)為0而其余位(比較多)為1的數(shù),不適合很多個(gè)連續(xù)1左移的方式來(lái)構(gòu)造,適合左移+位取反的方式來(lái)構(gòu)造。這里所說(shuō)bit4~bit10為0,那我就讓它bit4~bit10為1,然后取反。
(4)思路是:先試圖構(gòu)造出這個(gè)數(shù)的位相反數(shù),再取反得到這個(gè)數(shù)。比如本例中要構(gòu)造的數(shù)bit4~bit10為0,其余位為1,那我們就先構(gòu)造一個(gè)bit4~bit10為1,其余位為0的數(shù),然后對(duì)這個(gè)數(shù)按位取反即可。
int main(void)
{
//聰明的方法
unsigned int a;
a = ~(0x7f<<4);
printf("a = 0x%x.\n", a);
//笨方法
unsigned int a;
a = (0xf<<0) | (0x1fffff<<11);
printf("a = 0x%x.\n", a);
return 0 ;
}
3.4總結(jié):位與、位或結(jié)合特定二進(jìn)制數(shù)即可完成寄存器位操作需求
(1)如果你要的這個(gè)數(shù)比較少為1,大部分為0,則可以通過(guò)連續(xù)很多個(gè)1左移n位得到構(gòu)造數(shù)。
(2)如果你想要的數(shù)比較少為0,大部分為1,則可以通過(guò)構(gòu)造其位反數(shù),然后再取反來(lái)得到構(gòu)造數(shù)。
(3)如果你想要的數(shù)中連續(xù)1(連續(xù)0)的部分不止1個(gè),那么可以通過(guò)多段分別構(gòu)造,然后再彼此位或即可。這時(shí)候因?yàn)閰⑴c位或運(yùn)算的各個(gè)數(shù)為1的位是不重復(fù)的,所以這時(shí)候的位或其實(shí)相當(dāng)于幾個(gè)數(shù)的疊加。
4.位運(yùn)算實(shí)戰(zhàn)操作
4.1給定一個(gè)整型數(shù)a,設(shè)置a的bit3,保證其他位不變。
a = a | (1<<3); 或者 a |= (1<<3);
4.2給定一個(gè)整型數(shù),設(shè)置a的bit3~bit7,保持其他位不變。
a = a | (0b11111<<3); 或者 a |= (0x1f<<3);
4.3給定一個(gè)整型數(shù)a,清除a的bit15,保證其他位不變。
a = a & (~(1<<15)); 或者 a &= (~(1<<15));
4.4給定一個(gè)整型數(shù)a,清除a的bit15~bit23,保持其他位不變。
a = a & (~(0x1ff<<15)); 或者 a &= (~(0x1ff<<15));
4.5給定一個(gè)整型數(shù)a,取出a的bit3~bit8。
思路:
第一步,先將這個(gè)數(shù)bit3~bit8不變,其余位全部清零;
第二步,再將其右移3位得到結(jié)果;
第三步,想明白上面的2步算法,再將其轉(zhuǎn)換為C語(yǔ)言實(shí)現(xiàn)即可。
int main(void)
{
unsigned int a;
// 第一步,先將這個(gè)數(shù)bit3\~bit8不變,其余位全部清零
a &= (0x3f<<3);
// 第二步,再將其右移3位得到結(jié)果
printf("a = 0x%x.\n", a);
a >>= 3; //右移置底,即可解析為那個(gè)數(shù)值
printf("a = %u.\n", a);
return 0;
}
4.6用C語(yǔ)言給一個(gè)寄存器的bit7~bit17賦值937(其余位不受影響)。
關(guān)鍵:第一,不能影響其他位;第二,你并不知道原來(lái)bit7~bit17中裝的值。
思路:
第一步,先將bit7~bit17全部清零,當(dāng)然不能影響其他位; a &= (~(0x7ff<<7));
第二步,把937寫(xiě)入bit7~bit17即可。 a |= (937<<7);
int main(void)
{
unsigned int a = 0xc30288f8;
// 第一步,先將bit7~bit17全部清零
a &= (~(0x7ff<<7));
// 第二步,把937寫(xiě)入bit7~bit17
a |= (937<<7);
printf("a = 0x%x.\n", a);
return 0 ;
}
4.7用C語(yǔ)言將一個(gè)寄存器的bit7~bit17的值加17(其余位不受影響)。
關(guān)鍵:不知道原來(lái)的值是多少。
思路:
第一步,先讀出原來(lái)bit7~bit17的值;
第二步,給這個(gè)值加17;
第三步,將bit7~bit17清零;
第四步,將第二步算出來(lái)的值寫(xiě)入bit7~bit17。
int main(void)
{
unsiged int a = 0x0xc30288f8;
// 第一步,先讀出原來(lái)bit7~bit17的值;
unsigned int tmp;
tmp >>= 7;
// 第二步,給這個(gè)值加17;
tmp += 17;
// 第三步,將bit7~bit17清零;
a &= ~(0x7ff<<7);
// 第四步,將第二步算出來(lái)的值寫(xiě)入bit7~bit17。
a |= tmp;
printf("a = 0x%x.\n", a);
return 0 ;
}
4.8用C語(yǔ)言給一個(gè)寄存器的bit7~bit17賦值937,同時(shí)bit21~bit25的值加17。
思路:2倍的4.6代碼即可
int main(void)
{
unsigned int a = 0xc30288f8;
// bit7~bit17賦值937
a &= ~(0x7ff<<7);
a |= (937<<7);
// bit21~bit25的值加17
a &= ~(0x1f<<21);
a |= 17<<21;
printf("a = 0x%x.\n", a);
return 0 ;
}
分析:
上面寫(xiě)兩次代碼也可以,但是效率不高,可以將上面的兩步合二為一。
a &= ~( (0x7ff<<7) | (0x1f<<21));
a |= ((937<<7) | (17<<21))
擴(kuò)展:一般像937、17這樣的常數(shù),一般都會(huì)使用宏定義來(lái)代替,類(lèi)似于a |= ((VALUE1<<7) | (VALUE2<<21));
5.技術(shù)升級(jí):用宏定義來(lái)完成位運(yùn)算(面試??迹?/h1>
(1)用宏定義將32位數(shù)x的第n位(右邊起算,也就是bit0算第一位)置位。
#define SET_BIT_N(x, n) (x | (1U<<(n-1)))
(2)用宏定義將32位數(shù)x的第n位(右邊起算,也就是bit0算第一位)清零。
#define CLEAR_BIT_N(x, n) (x & ~(1U<<(n-1)))
(3)用宏定義將32位數(shù)x的第n位到第m位(右邊起算,也就是bit0算第一位,m是高位)置位。
假如n=3,m=6,題目就是要把bit2到bit5置位,我們需要一個(gè)算式來(lái)得到(m-n+1)個(gè)1。
思路:
第一步,先得到32位1:~0U
第二步,將第一步等到的數(shù)右移x位即可得到(m-n+1)個(gè)1,即(~0U >> (32-(m-n+1)))
得到最終式子:#define SET_BIT_N_M(x, n, m) (x | (((~0U) >> (32-(m-n+1)))<<(n-1)))
#include <stdio.h>
#define SET_BIT_N(x, n) (x | (1U<<(n-1)))
#define CLEAR_BIT_N(x, n) (x & ~(1U<<(n-1)))
#define SET_BIT_N_M(x, n, m) (x | (((~0U) >> (32-(m-n+1)))<<(n-1)))
int main(void)
{
// test foe SET_BIN_N
unsigned int a = 0;
unsigned int b = 0;
b = SET_BIT_N(a, 4);
printf("b = 0x%x.\n", b);
// test for CLEAR_BIT_N
unsigned int a = 0xFFFFFFFF;
unsigned int b = 0;
b = CLEAR_BIT_N(a, 4);
printf("b = 0x%x.\n", b);
// test for SET_BIT_N_M
unsigned int a = 0x0;
unsigned int b = 0;
b = SET_BIT_N_M(a, 1, 4);
printf("b = 0x%x.\n", b);
return 0;
}
6.著重分析復(fù)雜宏定義與位操作
題目:截取變量的部分連續(xù)位。變量0x88,也就是10001000b,若截取第2~4位,則值為:100b=4
Linux內(nèi)核宏定義:#define GETBITS(x, n, m) ((x & ~(~0U)<<(m-n+1))<<(n-1))>>(n-1))
分析:
(1)先分清楚這個(gè)復(fù)雜宏分為幾部分:2部分
(x & (0U)<<(m-n+1))<<(n-1)) 與 >>(n-1)
(2)繼續(xù)解析剩下的:又分為2部分
x 與 (0U)<<(m-n+1))<<(n-1)
(3)繼續(xù)分析:
~ 與 (~0U)<<(m-n+1)) 與>> (n-1)
優(yōu)先級(jí):~高于>>