Bài 31.426 phút đọc
Bitmask và trường bit
Sau bài này bạn sẽ làm được
- Thiết kế bộ cờ bằng bitmask và thao tác nhiều cờ cùng lúc
- Trích và ghi một trường nhiều bit bằng mặt nạ
- Khai báo trường bit và biết bố cục của nó do cài đặt quy định
- Chọn giữa bitmask thủ công và trường bit
Tám giá trị đúng sai trong một byte thay vì tám byte. C cho bạn hai cách làm việc đó, và một trong hai cách trông đẹp hơn nhưng không dùng được cho định dạng tệp hay giao thức mạng.
#Bitmask: nhiều cờ trong một biến
quyen.h
#ifndef QUYEN_H
#define QUYEN_H
#define BIT(n) (1u << (n))
/* Moi co la MOT bit. Dat ten ro rang, va dung enum
thay vi define de trinh go loi thay duoc ten. */
enum Quyen {
Q_DOC = BIT(0), /* 0x01 */
Q_GHI = BIT(1), /* 0x02 */
Q_THUC_THI = BIT(2), /* 0x04 */
Q_AN = BIT(3), /* 0x08 */
Q_HE_THONG = BIT(4), /* 0x10 */
Q_KHONG = 0,
Q_DOC_GHI = Q_DOC | Q_GHI,
Q_TAT_CA = Q_DOC | Q_GHI | Q_THUC_THI | Q_AN | Q_HE_THONG,
};
#endif /* QUYEN_H */| Việc | Biểu thức | Ghi chú |
|---|---|---|
| Bật một cờ | q |= Q_GHI | |
| Bật nhiều cờ | q |= Q_GHI | Q_AN | |
| Tắt một cờ | q &= ~Q_GHI | |
| Tắt nhiều cờ | q &= ~(Q_GHI | Q_AN) | Ngoặc bắt buộc |
| Đảo | q ^= Q_AN | |
| Có cờ này không | q & Q_GHI | Khác 0 nghĩa là có |
| Có bất kỳ cờ nào trong nhóm | q & (Q_DOC | Q_GHI) | |
| Có tất cả cờ trong nhóm | (q & can) == can | Chỗ hay bị nhầm nhất |
| Xóa hết | q = Q_KHONG | |
| Chỉ giữ nhóm này | q &= Q_DOC_GHI |
In bộ cờ ra chữ để gỡ lỗi
typedef struct { unsigned co; const char *ten; } MucCo;
static const MucCo BANG_CO[] = {
{ Q_DOC, "DOC" },
{ Q_GHI, "GHI" },
{ Q_THUC_THI, "THUC_THI" },
{ Q_AN, "AN" },
{ Q_HE_THONG, "HE_THONG" },
};
void in_quyen(unsigned q) {
int dau = 1;
if (q == 0) { printf("KHONG\n"); return; }
for (size_t i = 0; i < sizeof BANG_CO / sizeof BANG_CO[0]; ++i)
if (q & BANG_CO[i].co) {
printf("%s%s", dau ? "" : " | ", BANG_CO[i].ten);
dau = 0;
q &= ~BANG_CO[i].co;
}
if (q) printf("%s0x%X", dau ? "" : " | ", q); /* bit la con sot */
printf("\n");
}terminal
./quyen
in_quyen(0) -> KHONG in_quyen(Q_DOC|Q_GHI) -> DOC | GHI in_quyen(Q_TAT_CA) -> DOC | GHI | THUC_THI | AN | HE_THONG in_quyen(0x101) -> DOC | 0x100
#Trường nhiều bit bằng mặt nạ
Ví dụ thật: thanh ghi cấu hình 32 bit
/* Thanh ghi CR1 cua mot bo dinh thoi:
bit 31..16 chu ky
bit 15..12 bo chia truoc
bit 11..8 che do dem
bit 7 bat ngat
bit 6..4 nguon xung
bit 3..1 du tru
bit 0 bat */
#define CR1_CHU_KY_HI 31
#define CR1_CHU_KY_LO 16
#define CR1_CHIA_HI 15
#define CR1_CHIA_LO 12
#define CR1_NGUON_HI 6
#define CR1_NGUON_LO 4
#define CR1_NGAT 7
#define CR1_BAT 0
#define MAT_NA(hi, lo) ((~0u >> (31 - (hi))) & (~0u << (lo)))
static inline unsigned doc_truong(unsigned x, unsigned hi, unsigned lo) {
return (x & MAT_NA(hi, lo)) >> lo;
}
static inline unsigned ghi_truong(unsigned x, unsigned hi, unsigned lo,
unsigned v) {
return (x & ~MAT_NA(hi, lo)) | ((v << lo) & MAT_NA(hi, lo));
}
/* Dung: */
unsigned cr1 = 0;
cr1 = ghi_truong(cr1, CR1_CHU_KY_HI, CR1_CHU_KY_LO, 1000);
cr1 = ghi_truong(cr1, CR1_CHIA_HI, CR1_CHIA_LO, 3);
cr1 = ghi_truong(cr1, CR1_NGUON_HI, CR1_NGUON_LO, 2);
cr1 |= BIT(CR1_NGAT) | BIT(CR1_BAT);
unsigned chu_ky = doc_truong(cr1, CR1_CHU_KY_HI, CR1_CHU_KY_LO);#Trường bit của ngôn ngữ
Trường bit
Thành viên struct khai báo kèm số bit sau dấu hai chấm. Trình biên dịch tự sinh mã che và dịch, nên bạn viết
a.che_do = 5 thay vì các phép toán bit.struct Co1 {
unsigned bat : 1;
unsigned che_do : 3;
unsigned ma : 8;
};
struct Co1 a;
memset(&a, 0, sizeof a);
a.bat = 1;
a.che_do = 5;
a.ma = 0xAB;
unsigned m = a.ma; /* trinh bien dich tu sinh che va dich */terminal
gcc -std=c11 -Wall -Wextra -o bf bf.c && ./bf
sizeof(Co1) = 4
sizeof(Co2) = 8 (co unsigned : 0)
sizeof(Co3) = 1 (dung uint8_t)
Co1 { bat=1, che_do=5, ma=0xAB } trong bo nho: BB 0A 00 00 | Khai báo | sizeof trên x86-64 gcc | Vì sao |
|---|---|---|
| unsigned bat:1; unsigned che_do:3; unsigned ma:8; | 4 | Mười hai bit vừa trong một unsigned |
| Như trên, chèn thêm unsigned : 0; ở giữa | 8 | Trường rộng 0 ép trường sau sang đơn vị lưu trữ mới |
| uint8_t bat:1; uint8_t che_do:3; uint8_t ma:4; | 1 | Tám bit vừa trong một byte |
#Vì sao bit field không dùng cho giao thức
| Điều do cài đặt quy định | Hậu quả |
|---|---|
| Thứ tự xếp bit trong một đơn vị lưu trữ | gcc trên x86 xếp từ bit thấp lên, trên PowerPC big-endian thì ngược lại |
| Đơn vị lưu trữ là bao nhiêu byte | Cùng struct cho sizeof khác nhau giữa các trình biên dịch |
| Có được phép vắt một trường qua ranh giới đơn vị không | Một số cài đặt chèn đệm thay vì vắt |
| int x : 3 là có dấu hay không dấu | gcc chọn có dấu, một số trình biên dịch khác chọn không dấu |
| Kiểu nào được dùng làm trường bit | Chuẩn chỉ bảo đảm int, unsigned int và _Bool. uint8_t là mở rộng |
Bit field cho giao thức
/* Doc mot goi tin mang bang bit field */
struct GoiTinIP {
unsigned phien_ban : 4;
unsigned do_dai : 4;
unsigned dich_vu : 8;
unsigned tong_dai : 16;
};
struct GoiTinIP g;
memcpy(&g, bo_dem, sizeof g); /* HONG:
- thu tu bit khac nhau giua x86 va PowerPC
- sizeof co the la 4 hoac 8
- thu tu byte cua truong 16 bit cung khac */Che và dịch thủ công
/* Doc tung byte, tu che va dich */
static void doc_goi_tin(const uint8_t *b, GoiTinIP *g) {
g->phien_ban = (b[0] >> 4) & 0x0Fu;
g->do_dai = b[0] & 0x0Fu;
g->dich_vu = b[1];
g->tong_dai = ((uint16_t)b[2] << 8) | b[3]; /* lon truoc, dung chuan mang */
}
/* Dai hon, nhung cho ra CUNG KET QUA tren moi may.
Bai 18.8 da dung dung cach nay cho dinh dang tep. */#Chọn cách nào
| Tiêu chí | Bitmask thủ công | Trường bit |
|---|---|---|
| Đọc mã | Phải hiểu phép bit | Đọc như trường struct thường |
| Khả chuyển | Hoàn toàn | Bố cục do cài đặt quy định |
| Dùng cho tệp và mạng | Được | Không |
| Lấy địa chỉ trường | Không cần, nó là biến thường | Không được |
| Trình biên dịch kiểm khoảng giá trị | Không | Có, nhưng chỉ với hằng số |
| Nhiều cờ một lúc | Dễ, OR các mặt nạ | Phải gán từng trường |
| Tốc độ | Như nhau | Như nhau, cùng sinh ra lệnh che và dịch |
| Truyền cả bộ cờ qua tham số | Một unsigned | Cả struct, hoặc một union |
Ví dụ hoàn chỉnh: cả hai cách cho cùng bài toán
/* Bitmask thu cong */
#define CH_BAT BIT(0)
#define CH_CHE_DO_HI 3
#define CH_CHE_DO_LO 1
#define CH_MA_HI 11
#define CH_MA_LO 4
static inline unsigned ch_che_do(uint32_t x) {
return doc_truong(x, CH_CHE_DO_HI, CH_CHE_DO_LO);
}
static inline uint32_t ch_dat_che_do(uint32_t x, unsigned v) {
return ghi_truong(x, CH_CHE_DO_HI, CH_CHE_DO_LO, v);
}
/* Truong bit */
typedef struct {
unsigned bat : 1;
unsigned che_do : 3;
unsigned ma : 8;
} CauHinh;
/* Ca hai deu sinh ra khoang cung mot ma may.
Khac biet nam o KHA CHUYEN va o CACH DOC. */terminal
# Hai bản sinh ra hợp ngữ giống hệt nhau
gcc -O2 -S -masm=intel -o - ch1.c ch2.c
che va dich, ham f:
mov eax, ecx
shr eax
and eax, 7
ret
truong bit, ham g:
mov eax, ecx
shr eax
and eax, 7
retTự làm thử
- Viết bộ cờ quyền bằng
enumvớiBIT(n)và thêm_Static_assertkiểm không chồng bit. - Viết
in_quyenvà chạy nó với 0, hai cờ, tất cả cờ, và một giá trị có bit lạ. - Phân biệt
q & canvới(q & can) == canbằng một ví dụ cụ thể. - Khai báo
struct Co1ở trên, gán ba giá trị, rồi in bộ nhớ dạng hex và giải mã bằng tay. - Thêm
unsigned : 0;vào giữa struct và sosizeoftrước sau. - Khai báo
struct { int x : 3; }, gán 4, và giải thích vì sao đọc ra -4.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Bộ cờ khai báo bằng
enumvớiBIT(n), và phải viết rõ giá trị vìenumtự đánh số 0, 1, 2, 3. - Kiểm nhiều cờ:
q & canlà bất kỳ,(q & can) == canlà tất cả. - Số bit của trường là
hi - lo + 1, và dấu cộng một rất hay bị quên. - Trường bit đọc dễ hơn nhưng bố cục do cài đặt quy định, nên không dùng cho tệp, mạng hay thanh ghi.
int x : 3là có dấu trên gcc, nên gán 4 đọc ra -4. Luôn viết rõunsigned.