C語言沒有類似於 Java 的「垃圾回收」等高級程式語言特性,也不像 python 那樣無需顯示聲明類型就能使用變量,因此在很多人看來,C語言有些「低級」。但是C語言的這些「低級」也是 C語言的優點——使用C語言開發程序,程式設計師能夠準確知道究竟使用了多少資源,以及哪些資源還在內存裡,哪些已經被釋放。換句話說,C語言程序具備資源的使用確定性。
因此,C語言特別適合用於一些資源比較匱乏的項目開發中。在這些項目中,以嵌入式項目為代表,一般都需要嚴格控制內存的使用——使用 1 個字節(Byte)就能存放的值,絕對不定義 2 個字節寬度的變量。甚至,一些「摳門」的C語言程式設計師會將 1 個字節掰成若干個位(bit)使用。
所以,在C語言程序開發中,常常需要操作某個變量特定的位(bit),這對於C語言來說當然沒有任何難度,各種移位操作就能夠方便的解決該類需求,例如:
unsignedchar status;status |= 0x01 << 2;status &= ~0x01;上面第二行C語言代碼將 status 的第3個位(bit 2)設置為 1,第三行C語言代碼將 status 的第1個位(bit 0)設置為 0。可以看出,藉助於位運算,C語言可以比較簡單的操作 status 的指定位。不過,C語言這種操作位的方法有時候看起來不夠直觀——至少沒有直接賦值那麼直觀。
那C語言有沒有更加直觀的位操作方法呢?
上面的例子通過移位、以及或與非等操作實現對變量 status 的位操作,但是看起來卻不是那麼直觀,那麼C語言有沒有更加直觀的位操作方法呢?似乎可以藉助C語言的聯合體(union)和位域(bit field)語法,間接的實現位操作,請看下面的C語言代碼:
union convert{ unsignedchar status;struct __bits { unsignedchar bit0:1;unsignedchar bit1:1;unsignedchar bit2:1;unsignedchar bit3:1;unsignedchar bit4:1;unsignedchar bit5:1;unsignedchar bit6:1;unsignedchar bit7:1; }v; };
此例中 status 和 bits 結構體共享一個字節的內存空間,結構體 bits 利用C言中的位域語法將一個字節的內存空間拆分成 8 個位,這種情況下,要讀寫 status 的位就非常簡單了,請看下面的C語言示例代碼:
union convert cv;// 寫 status 的 bit 3cv.v.bit3 = 0;// 寫 status 的 bit 7cv.v.bit7 = 1;// 讀 status 的 bit 5r = cv.v.bit5;編寫 main() 函數,測試通過位域操作 status 的位,相關C語言代碼如下,請看:
int main() { union convert cv; cv.status = 0; cv.v.bit3 = 1; cv.v.bit1 = 1;printf("%d\n", cv.status);return0; }
main() 函數一開始將 status 置為 0,然後將它的 bit1 和 bit3 設置為 1,也就相當於將 status 設置為 0x0a,編譯並執行這段C語言代碼,得到如下輸出:
# gcc t.c# ./a.out 10一切與預期一致,這樣就實現了以「賦值」的形式,操作C語言中的位,而且看起來比「移位與或非操作」更加直觀,所以這樣的操作更好了?
事實上,有一些C語言程式設計師的確這麼用,至少我的一些同事喜歡這樣的位操作。
警惕「常識陷阱」
1 個字節(Byte)等於 8 個位(bit)似乎已經是程式設計師間的常識了,很少有人質疑這一點。但是作為C語言程式設計師,我們常常要在不同的硬體平臺上做底層開發,應該明白:1個字節等於8個位只是慣例而已,C標準並沒有定義這一點。有些編譯器並不遵守這個慣例,例如,在 Texas 的 C55x DSP 的平臺上,1 個字節等於 16 個位。在這個平臺上,各種數據類型佔用的位數有些奇怪:
以 long long 為例,在該平臺上 long long 之所以等於 40 bit,而不是我們常用的 64 bit,是因為它們的 ALU 是 40 bit 寬,因此編譯器規定 long long 為 40 bit 可以降低功耗和提升效率。
另外, 就算在 1 個字節等於 8 個位的硬體平臺上,單個字節的 8 個位是如何分布的也是沒有明確標準的。