一些實用的C語言小技巧
每天一點C / 位和字節(jié)
正文目錄:
1. 位相關的運算符
2. 位相關的用法
3. 位字段 (bit field)
4. 怎樣判斷機器的字節(jié)順序?
5. 怎樣將整數轉換到二進制或十六進制?
6. 怎樣高效地統(tǒng)計整數中為1的位的個數?
7. 相關參考
寫作目的:
-
記錄一些 C 語言中位和字節(jié)相關的操作。
測試環(huán)境:
-
Ubuntu 16.04 -
gcc version 5.4.0
1. 位相關的運算符
1) 取反:~
~(10011010) = (01100101)
運算符 ~ 把 1 變?yōu)?0,把 0 變?yōu)?1。
2) 按位與:&
(10010011) & (00111101) = (00010001)
運算符 & 通過逐位比較兩個運算對象,生成一個新值。對于每個位,只有兩個運算對象中相應的位都為 1,結果才為 1。
3) 按位或:|
(10010011) | (00111101) = (10111111)
運算符 | 通過逐位比較兩個運算對象,生成一個新值。對于每個位,如果兩個運算對象中有 >=1 的位為 1,結果就為 1。
4) 按位異或:^
(10010011) ^ (00111101) = (10101110)
運算符 ^ 逐位比較兩個運算對象。對于每個位,如果兩個運算對象中有且只有 1 位 為 1, 結果為 1。
5) 左移:<<
(10001010) << 2 = (00101000)
運算符 << 將其左側運算對象每一位的值向左移動其右側運算對象指定的位數。左側運算對象移出左末端位的值會被丟棄,用 0 填充空出的位置。
6) 右移:>>
(10001010) >> 2 = (00100010) // 情況1
(10001010) >> 2 = (11100010) // 情況2
運算符 >> 將其左側運算對象每一位的值向右移動其右側運算對象指定的位數。左側運算對象移出右末端位的值丟。
對于無符號類型,用 0 填充空出的位置。
對于有符號類型,其結果取決于機器。空出的位置可能用 0 填充,也可能用符號位填充。
2. 位相關的用法
1) 什么是掩碼?
所謂掩碼指的是一些設置為開 (1) 或關 (0) 的位組合。
為什么叫掩碼?看下面這個例子:
#define MASK (1<<1)
flags = flags & MASK;
上面這個例子中,只有 MASK 中 為1的位才可見,掩碼中的 0 隱藏 (掩蓋) 了 flags 中相應的位。
2) 打開 (設置) 位
有時,比如在操作硬件寄存器的情況下,需要打開一個值中的特定位,同時保持其他位不變。這種情況可以使用按位或運算符 | 和一個掩碼進行配合:
#define MASK (1<<1)
flags |= MASK;
3) 關閉 (清空) 位
在不影響其他位的情況下關閉指定的位:
#define MASK (1<<1)
flags &= ~MASK;
4) 切換位
切換位指的是打開已關閉的位,或關閉已打開的位:
#define MASK (1<<1)
flags ^= MASK;
5) 檢查位
檢查某位的值是否為 1:
#define MASK (1<<1)
(flags & MASK) == MASK
注意,掩碼至少要與其覆蓋的值的寬度相同,要避免符號位帶來的意外,最好在代碼中使用 unsigned int 操作位和字節(jié)。
6) 提取位
移位運算符可用于從較大單元中提取一些位,例如提取 RBG 顏色值:
#define BYTE_MASK 0xff
unsigned long color = 0x123456;
unsigned char blue, green, red;
red = color & BYTE_MASK;
green = (color >> 8) & BYTE_MASK;
blue = (color >> 16) & BYTE_MASK;
3. 位字段 ( bit field )
位字段通過一個結構聲明來建立,該結構聲明為每個字段提供標簽,并確定該字段的寬度,在 Linux 驅動中,某些代碼使用了位字段:
struct ap_queue_status {
unsigned int queue_empty : 1;
...
unsigned int response_code : 8;
unsigned int pad2 : 16;
} aqs;
給字段賦值:
aqs.queue_empty = 0;
aqs.response_code = 0xff;
所賦的值不能超出字段可容納的范圍。
位字段占用的空間:
struct {
unsigned int autfd : 1;
unsigned int bldfc : 1;
unsigned int undln : 1;
unsigned int itals : 1;
} prnt;
struct {
unsigned int code1 : 2;
unsigned int code2 : 2;
unsigned int code3 : 6;
unsigned int code4 : 8;
#if TEST
unsigned int code5 : 10;
unsigned int code6 : 12;
unsigned int code7 : 24;
#endif
} prcode;
int main(void)
{
printf("%ld %ld\n", sizeof(prnt), sizeof(prcode));
}
測試結果:
4 4 // without TEST
4 12 // with TEST
系統(tǒng)會自動判斷出需要幾個 byte 的空間來存儲數據,在我的機器上測試,一個成員最起碼占用 1 個 byte。
位字段的儲存順序:
取決于機器。在有些機器上,存儲的順序是從左往右,而在另一些機器上,是從右往左。另外,不同的機器中兩個字段邊界的位置也有區(qū)別。由于這些原因,位字段通常都不容易移植,我不要求自己寫,但是要求自己會看。
4. 怎樣判斷機器的字節(jié)順序?
演示 demo:
int main(void)
{
int x = 1;
if (*((char *)&x) == 1)
printf("little - endian\n");
else
printf("big - endian\n");
return 0;
}
運行效果:
$ gcc byte_order.c -o byte_order
$ ./byte_order
little - endian
代碼解析:
-
先初始化在內存中占用 4 個字節(jié)的 int 變量。
-
然后獲取int 變量中第 1 個字節(jié)的地址,等效代碼是:char *px = (char *)&x。
-
最后獲取第 1 個字節(jié)的值:*px,觀察 *px 是否為 1 就可以知道大小端了。
5. 怎樣將整數轉換到二進制或十六進制?
演示 demo:
進行任意進制數轉換的小函數:
#define BUF_SIZE (33)
char *baseconv(unsigned int num,int base)
{
static char retbuf[BUF_SIZE];
char *p;
...
p = &retbuf[sizeof(retbuf)-1];
*p='\0';
do {
*--p="0123456789abcdef"[num % base];
num /=base;
} while(num !=0);
return p;
}
在 main() 中進行測試:
int main(void)
{
int a = 20;
printf("%s\n", baseconv(a, 2));
printf("%s\n", baseconv(a, 16));
return 0;
}
運行效果:
$ gcc int_conv.c -o int_conv
$ ./int_conv
10100
14
代碼解析:
-
首先需要明確的是:整數本來就是以二進制存儲的,這里說的轉換只是指打印的形式。
-
在baseconv() 中的緩沖是 static 的,這有2 個作用:1) 將緩沖清 0,2) 只有是 static 的緩沖才能在函數外部被使用。
-
注意 char *p = &retbuf[sizeof(retbuf)-1]?。健?\0' 這個操作,這里將緩沖的最高位設置為字符串結束符,同時表明了字符串是從高地址向底地址構造的,函數返回緩沖中有效數據的起始地址。
-
如果你這樣打?。?/p>
printf("%d %s %s\n", a, baseconv(a, 2), baseconv(a, 16));會得到這樣的結果:
10100 00。這是因為 baseconv() 中的緩沖是 static 的,
baseconv(a, 2)將baseconv(a, 16)沖刷掉了。
6. 怎樣高效地統(tǒng)計整數中為1的位的個數?
演示 demo:
統(tǒng)計整數中為1的位的個數的小函數:
static int bitcounts[] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
int bitcount(unsigned int u)
{
int n=0;
for(; u!=0; u>>=4)
n += bitcounts[u & 0x0f];
return n;
}
在 main() 中進行測試:
int main(void)
{
int i = 0;
for (i=0; i<=0x0f; i++)
printf("%d\n", bitcount(i));
return 0;
}
運行效果:
$ gcc bit_counts.c -o bit_counts
$ ./bit_counts
0
1
1
2
1
2
2
3
1
2
2
3
2
3
3
4
代碼解析:
-
許多像這樣的位問題可以使用查找表格來提高效率和速度。
-
這段代碼是以每次 4 位的方式計算數值中為1的位的個數。
相關參考
-
《C Primer Plus 6th》, 15 -
《你必須知道的 495 個 C語言問題》, 20.7 -
《C 和指針》, 5.1.3 -
《C 專家》, NULL -
《C 和 C++ 程序員面試秘籍》, 5 -
《C 語言解惑》, NULL
思考技術,也要思考人生
學習技術,更要學習如何生活。
你和我各有一個蘋果,如果我們交換蘋果的話,我們還是只有一個蘋果。但當你和我各有一個想法,我們交換想法的話,我們就都有兩個想法了。
對 嵌入式系統(tǒng) (Linux、RTOS、OpenWrt、Android) 和 開源軟件 感興趣,想和更多人互相交流學習,關注公眾號:嵌入式Hacker,一起來學習吧。
關注 / 轉發(fā) / 打賞,都是對作者莫大的支持。覺得文章對你有價值的話,不妨點個 在看和點贊 哦。祝工作順利,家庭幸福,財源滾滾~
猜你喜歡
C語言對象編程第一彈:封裝與抽象
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯系我們,謝謝!





