C 語言中的位掩碼
Jinku Hu
2023年10月12日
本文將演示關於如何在 C 語言中使用位掩碼的多種方法。
使用 struct
關鍵字在 C 語言中定義位掩碼資料
位掩碼通常用於位性操作,以訪問或設定位場式資料結構的各個部分。另一方面,利用位域來有效地儲存資料,減少記憶體佔用。同時,位智運算在硬體上的執行速度相對比普通的算術運算要快。在下面的例子中,我們使用 struct
關鍵字來演示位域的實現。
請注意,這是一種特殊的符號,用來構造物件,在這個物件中,可以使用傳統的成員訪問操作符來檢索給定的位域。Bitfield
結構在記憶體中儲存了一個佔用 32 位的無符號整數,但它也可以以 3 個不同大小的部分–23 位、5 位和 4 位的值來訪問,分別命名為 year
、day
和 month
。因此,Bitfield
代表了在記憶體中有效實現的日期抽象。
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
struct {
uint32_t year : 23;
uint32_t day : 5;
uint32_t month : 4;
} typedef Bitfield;
int main() {
Bitfield date = {2020, 13, 12};
printf("sizeof Bitfield: %lu bytes\n", sizeof(date));
printf("date: %d/%d/%d \n", date.day, date.month, date.year);
return EXIT_SUCCESS;
}
輸出:
sizeof Bitfield: 4 bytes
date: 13/12/2020
使用 struct
結合 union
在 C 語言中定義位掩碼資料
另外,我們也可以在前面的結構中加入 union
關鍵字,這樣就可以單獨訪問整個 32 位數。由於訪問位欄位成員比訪問 struct
的成員要慢,所以我們將使用位運算把日期值分配給單獨的 ydm
整數成員。
請注意,代表給定日期的十進位制數在邏輯上是相互或
的,但在此之前,day
和 month
的值分別左移 23 位和 28 位。後面的數字是根據這些成員在位欄位中所佔的對應位位置來取的。注意,當需要列印輸出時,仍可分別訪問每個成員。
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
union {
struct {
uint32_t year : 23;
uint32_t day : 5;
uint32_t month : 4;
};
uint32_t ydm;
} typedef BitfieldFast;
int main() {
BitfieldFast date_f;
date_f.ydm = 2020 | (13 << 23) | (12 << 28);
printf("sizeof BitfieldFast: %lu bytes\n", sizeof(date_f));
printf("date_f: %d/%d/%d \n", date_f.day, date_f.month, date_f.year);
return EXIT_SUCCESS;
}
輸出:
sizeof BitfieldFast: 4 bytes
date_f: 13/12/2020
位掩碼的另一個典型使用例子是網路中的 IP 地址。也就是說,IP 地址提供了網路掩碼,它決定了給定地址屬於哪個網路。網路地址的計算是通過對 IP 地址和它的網路掩碼進行邏輯與
運算來完成的。在這種情況下,我們定義了位欄位 struct
來分別儲存 IP 地址和網路掩碼。請注意,邏輯與運算是在整個 32 位的值上完成的,但是當我們把地址列印成 4 個 8 位的部分時,使用的是成員訪問運算子。
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
union {
struct {
uint8_t first : 8;
uint8_t second : 8;
uint8_t third : 8;
uint8_t fourth : 8;
};
uint32_t ip;
} typedef IPAddress;
int main() {
IPAddress ip1 = {10, 127, 5, 1};
IPAddress mask = {255, 255, 240, 0};
printf("ip1: %d.%d.%d.%d\n", ip1.first, ip1.second, ip1.third, ip1.fourth);
ip1.ip = ip1.ip & mask.ip;
printf("net: %d.%d.%d.%d\n", ip1.first, ip1.second, ip1.third, ip1.fourth);
return EXIT_SUCCESS;
}
輸出:
ip1: 10.127.5.1
net: 10.127.0.0