C語言位操作 此文將花費您8~15分鐘時間,帶您對嵌入式中常用的位操作有個了解。和數字電路有點相似!感謝閱讀!位操作符 1.位與&
1、注意:位與符號是一個&,兩個&&是邏輯與。
2、真值表:1&0=01&1=10&0=00&1=0
3、從真值表可以看出:位與操作的特點是,只有1和1位與結果為1,其余全是0.
4、位與和邏輯與的區別:位與時兩個操作數是按照二進制位彼次對應位相與的,邏輯與是兩個操作數作為整體來相與的。(舉例:0xAA&0xF0=0xA0,0xAA && 0xF0=1)
2.位或|
1、注意:位或符號是一個|,兩個||是邏輯或。
2、真值表:1|0=11|1=10|0=00|1=1
3、從真值表可以看出:位或操作的特點是:只有2個0相位或才能得到0,只要有1個1結果就一定是1.
4、位或和邏輯或的區別:位或時兩個操作數是按照二進制位彼次對應位相與的,邏輯或是兩個操作數作為整體來相或的。
3.位取反~
1、注意:C語言中位取反是~,C語言中的邏輯取反是!
2、按位取反是將操作數的二進制位逐個按位取反(1變成0,0變成1);而邏輯取反是真(在C語言中只要不是0的任何數都是真)變成假(在C語言中只有0表示假)、假變成真。
實驗: #include <stdio.h>
void main()
{
int a = 5; // 結果 :
printf("~~a = %d.\n",~~a);//~~a = 5.
printf("!!a = %d.\n",!!a);//!!a = 1.
} 結論: @任何非0的數被按位取反再取反就會得到他自己;任何非0的數被按邏輯取反再取反就會得到1; @任何非0的數被按邏輯取反再取反就會得到1;
4.位異或^
1、位異或真值表: 1^1=0 0^0=0 1^0=1 0^1=1 2、位異或的特點:2個數如果相等結果為0,不等結果為1。 記憶方法:異或就是相異就或操作起來。 位與、位或、位異或的特點總結: 位與:(任何數,其實就是1或者0)與1位與無變化,與0位與變成0
位或:(任何數,其實就是1或者0)與1位或變成1,與0位或無變化
位異或:(任何數,其實就是1或者0)與1位異或會取反,與0位異或無變化
5.左移位<< 與右移位>>
C語言的移位要取決于數據類型。
對于無符號數,左移時右側補0(相當于邏輯移位)
對于無符號數,右移時左側補0(相當于邏輯移位) 對于有符號數,左移時右側補0(叫算術移位,相當于邏輯移位) 對于有符號數,右移時左側補符號位(如果正數就補0,負數就補1,叫算術移位)實驗: #include <stdio.h> void main()
{
int a = 5; // 0101b
int b = -5;// 1...0101b
printf("a<<2 = %d.\n",a<<2);
printf("a>>2 = %d.\n",a>>2);
printf("b<<2 = %d.\n",b<<2);
printf("b>>2 = %d.\n",b>>2);
} 結果: a<<2 = 20.
a>>2 = 1.
b<<2 = -20.
b>>2 = -2.結論: 左移一位,結果*2; 右移一位,結果/2; 注釋:有符號數右移結果往小的那邊走,-5/4=-1.25->-2; 分析過程自己去分析數值在計算機內部的存儲(以補碼形式存儲!) ~嵌入式中研究的移位,以及使用的移位都是無符號數
位與位或位異或在操作寄存器時的特殊作用 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進行位與即可。 #include <stdio.h>
void main()
{
printf("0x%X.\n",0xAAAAAAAA&0xFFFF00FF);// 0xAAAA00AA.
}
3.特定位置1用|
1、回顧上面講的位或操作的特點:任何數,其實就是1或者0)與1位或變成1,與0位或無變化
2、操作手法和剛才講的位與是類似的。我們要構造這樣一個數:要置1的特定位為1,其他位為0,然后將這個數與原來的數進行位或即可。 3、舉例: 假設原來32位寄存器中的值為:0xAAAAAAAA,我們希望將bit8~bit15置1而其他位不變,可以將這個數與0x0000FF00進行位或即可。 #include <stdio.h>
void main()
{
printf("0x%X.\n",0xAAAAAAAA|0x0000FF00);// 0xAAAAFFAA.
}
4.特定位取反用^
1、回顧上面講的位異或操作的特點:(任何數,其實就是1或者0)與1位異或會取反,與0位異或無變化
2、操作手法和剛才講的位與是類似的。我們要構造這樣一個數:要取反的特定位為1,其他位為0,然后將這個數與原來的數進行位異或即可。
進行位或即可。 3、舉例: 假設原來32位寄存器中的值為:0xAAAAAAAA,我們希望將bit8~bit15取反而其他位不變,可以將這個數與0x0000FF00進行異或即可。 #include <stdio.h>
void main()
{
printf("0x%X.\n",0xAAAAAAAA^0x0000FF00);// 0xAAAA55AA.
} 如何用位運算構建特定二進制數 1.寄存器位操作經常需要特定位給特定值
1、從上節可知,對寄存器特定位進行置1或者清0或者取反,關鍵性的難點在于要事先構建一個特別的數,這個數和原來的值進行位與、位或、位異或等操作,即可達到我們對寄存器操作的要求。
2、 解法1:用工具軟件或者計算器或者自己大腦計算,直接給出完整的32位特定數。 優勢:可以完成工作,難度也不大,操作起來也不是太麻煩。
劣勢:依賴工具,而且不直觀,讀程序的人不容易理解。
評價:湊活能用,但是不好用,應該被更好用的方法替代。
3、解法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的數。怎么做?
2、利用上面講的方法就可以:(0xf<<0)|(0x1fffff<<11) 但是問題是:連續為1的位數太多了,這個數字本身就很難構造,所以這種方法的優勢損失掉了。 3、這種特定位(比較少)為0而其余位(大部分)為1的數,不適合用很多個連續1左移的方式來構造,適合左移加位取反的方式來構造。
4、思路是:先試圖構造出這個數的位相反數,再取反得到這個數。
(譬如本例中要構造的數bit4~bit10為0其余位為1,那我們就先構造一個bit4~bit10為1,其余位為0的數,然后對這個數按位取反即可)- ~(0x7f<<4)
4.總結:位與、位或結合特定二進制數即可完成寄存器位操作需求
1、如果你要的這個數比較少位為1,大部分位為0,則可以通過連續很多個1左移n位得到。
2、如果你想要的數是比較少位為0,大部分位為1,則可以通過先構建其位反數,然后再位取反來得到。
3、如果你想要的數中連續1(連續0)的部分不止1個,那么可以通過多段分別構造,然后再彼此位或即可。 這時候因為參與位或運算的各個數為1的位是不重復的,所以這時候的位或其實相當于幾個數的疊加。
位運算實戰演練1 回顧:要置1用|,用清零用&,要取反用^,~和<< >>用來構建特定二進制數。
1.給定一個整型數a,設置a的bit3,保證其他位不變。
a = a | (1<<3)或者 a |= (1<<3)
2.給定一個整形數a,設置a的bit3~bit7,保持其他位不變。
a = a | (0b11111<<3)或者 a |= (0x1f<<3);
3.給定一個整型數a,清除a的bit15,保證其他位不變。
a = a & (~(1<<15));或者 a &=(~(1<<15));
4.給定一個整形數a,清除a的bit15~bit23,保持其他位不變。
a = a & (~(0x1ff<<15));或者 a &=(~(0x1ff<<15));
5.給定一個整形數a,取出a的bit3~bit8。
思路:
第一步:先將這個數bit3~bit8不變,其余位全部清零。
第二步,再將其右移3位得到結果。
第三步,想明白了上面的2步算法,再將其轉為C語言實現即可。
a &= (0x3f<<3);
a >>= 3;
6.用C語言給一個寄存器的bit7~bit17賦值937(其余位不受影響)。
關鍵點:第一,不能影響其他位;第二,你并不知道原來bit7~bit17中裝的值。
思路: 第一步,先將bit7~bit17全部清零,當然不能影響其他位。 第二步,再將937寫入bit7~bit17即可,當然不能影響其他位。 a &= ~(0x7ff<<7); a |= (937<<7);
位運算實戰演練2 1.用C語言將一個寄存器的bit7~bit17中的值加17(其余位不受影響)。
關鍵點:不知道原來的值是多少
思路: 第一步,先讀出原來bit7~bit17的值 第二步,給這個值加17
第三步,將bit7~bit17清零
第四步,將第二步算出來的值寫入bit7~bit17
tmp = a&(0x7ff<<7); tmp>>=7; tmp+=17; a &=~(0x7ff<<7); a|=(tmp<<7); 2.用C語言給一個寄存器的bit7~bit17賦值937,同時給bit21~bit25賦值17.
思路:6.的升級版,兩倍的6.中的代碼即可解決。
分析:這樣做也可以,但是效果不夠高,我們有更優的解法就是合兩步為一步。
a &= ~((0x7ff<<7) |(0x1f<<21)); a |= ((937<<7) | (0x17<<21)) 技術升級:用宏定義來完成位運算 1.直接用宏來置位、復位(最右邊為第1位)
#define SET_NTH_BIT(x, n) (x | (1<<(n-1)))
#define CLEAR_NTH_BIT(x, n) (x &~(1<<(n-1)))
2.截取變量的部分連續位。例如:變量0x88, 也就是0b10001000,若截取第2~4位,則值為:0b100 = 4 #define GETBITS(x, n, m) ((x &~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1))
分析:這個題目相當于我們位運算實戰演練1中5.做的事情,只不過要用宏來實現。
這個題目相當于是要把x的bit(n-1)到bit(m-1)取出來 注:優先級~ 高于 <<高于& #define GETBITS(x, n, m) ((x &(~(~(0U)<<(m-n+1))<<(n-1))) >> (n-1))
U表示unsigned int-32
復雜宏怎么分析:
((x & ~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1))
第一步,先分清楚這個復雜宏分為幾部分:2部分 @(x &~(~(0U)<<(m-n+1))<<(n-1)) @>>(n-1) 分析為什么要>>(n-1),相當于是我們5.中的第二步(第二步,再將其右移3位得到結果。)
第二步,繼續解析剩下的:又分為2部分
@x& @~(~(0U)<<(m-n+1))<<(n-1) 分析為什么要&,相當于我們5中的第一步 (第一步:先將這個數bit3~bit8不變,其余位全部清零。)
第三步,繼續分析剩下的:
~ (~(0U)<<(m-n+1))<<(n-1)
這個分析時要搞清楚第2坨到底應該先左邊取反再右邊<<;還是先右邊<<再左邊取反。
解法:第一,查C語言優先級表;第二,自己實際寫個代碼測試。
說明這個式子應該是 ~(~(0U)<<(m-n+1))<<(n-1) ,這就又分為2部分了
0x88:10001000 例如:變量0x88, 也就是0b10001000,若截取第2~4位,則值為:0b100 = 4 ~(~(0U)<<(m-n+1))<<(n-1)):00001110
(x & ~(~(0U)<<(m-n+1))<<(n-1)):00001000
(x & ~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1):00001000
|