Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 31.324 phút đọc

Bật, tắt, đảo và kiểm tra bit

Sau bài này bạn sẽ làm được

  • Bật, tắt, đảo và kiểm tra một bit bằng biểu thức đúng
  • Viết bộ macro thao tác bit an toàn
  • Nhận ra lỗi tràn khi dịch quá ba mươi mốt bit
  • Đọc và ghi một trường nhiều bit liền nhau

Bốn thao tác: bật, tắt, đảo, kiểm tra. Mỗi cái một biểu thức, và bốn biểu thức đó xuất hiện trong mọi trình điều khiển thiết bị từng được viết.

#Bốn thao tác cơ bản

Cùng một mặt nạ, ba phép toán khác nhau. Chỉ bit thứ hai bị ảnh hưởng, mọi bit khác giữ nguyên.
Thao tácBiểu thứcVì sao nó chạy
Bật bit nx |= 1u << nOR với 1 luôn cho 1, OR với 0 giữ nguyên
Tắt bit nx &= ~(1u << n)AND với 0 luôn cho 0, AND với 1 giữ nguyên
Đảo bit nx ^= 1u << nXOR với 1 đảo, XOR với 0 giữ nguyên
Kiểm bit n(x >> n) & 1uKéo bit về vị trí 0 rồi che mọi bit khác
terminal
./bit-thao-tac
x             10110001  = 0xB1 = 177
BIT(2)        00000100  = 0x04 = 4
x |= BIT(2)   10110101  = 0xB5 = 181
x &= ~BIT(2)  10110001  = 0xB1 = 177
x ^= BIT(2)   10110101  = 0xB5 = 181
KIEM_BIT(x, 2) = 0,  KIEM_BIT(x, 4) = 1

#Bộ macro nên có

bit.h
#ifndef BIT_H
#define BIT_H

#include <stdint.h>

/* Mat na chi co bit n bang 1 */
#define BIT(n)            (1u << (n))
#define BIT64(n)          (1ULL << (n))

/* Bon thao tac co ban */
#define BAT_BIT(x, n)     ((x) |=  BIT(n))
#define TAT_BIT(x, n)     ((x) &= ~BIT(n))
#define DAO_BIT(x, n)     ((x) ^=  BIT(n))
#define KIEM_BIT(x, n)    (((x) >> (n)) & 1u)

/* Mat na cho khoang bit tu lo den hi, ca hai dau mut */
#define MAT_NA(hi, lo)    ((~0u >> (31 - (hi))) & (~0u << (lo)))

/* Doc va ghi mot truong nhieu bit */
#define DOC_TRUONG(x, hi, lo)      (((x) & MAT_NA(hi, lo)) >> (lo))
#define GHI_TRUONG(x, hi, lo, v)                                    \
    ((x) = ((x) & ~MAT_NA(hi, lo))                                  \
         | (((unsigned)(v) << (lo)) & MAT_NA(hi, lo)))

#endif /* BIT_H */

#Ghi một giá trị đúng sai vào bit

/* Cach 1: re nhanh */
#define GHI_BIT(x, n, v)  ((v) ? BAT_BIT(x, n) : TAT_BIT(x, n))

/* Cach 2: khong re nhanh */
x = (x & ~BIT(n)) | ((unsigned)!!(v) << (n));
terminal
./ghi-bit
ban an toan: x = 8 (mong doi 8)
sau khi ghi 0: x = 0 (mong doi 0)

#Thao tác nhiều bit cùng lúc

/* Mat na co the co nhieu bit, va bon phep toan van dung nguyen */

#define CO_DOC     BIT(0)
#define CO_GHI     BIT(1)
#define CO_THUC_THI BIT(2)
#define CO_AN      BIT(3)

unsigned quyen = 0;

quyen |= CO_DOC | CO_GHI;              /* bat HAI co mot luc */
quyen &= ~(CO_GHI | CO_AN);            /* tat HAI co mot luc */
quyen ^= CO_THUC_THI;                  /* dao mot co */

if (quyen & CO_DOC)                    /* co it nhat CO_DOC */
if ((quyen & (CO_DOC | CO_GHI)) == (CO_DOC | CO_GHI))   /* co CA HAI */
if (quyen & (CO_DOC | CO_GHI))         /* co it nhat MOT trong hai */

Đọc và ghi một trường nhiều bit liền nhau

/* Thanh ghi cau hinh 8 bit:
     bit 7..4  toc do  (0 den 15)
     bit 3..2  che do  (0 den 3)
     bit 1     bat
     bit 0     ngat */

unsigned cau_hinh = 0xB6;      /* 10110110 */

unsigned toc_do = (cau_hinh & 0xF0) >> 4;     /* 1011 = 11 */
unsigned che_do = (cau_hinh & 0x0C) >> 2;     /* 01   = 1  */
unsigned bat    = (cau_hinh >> 1) & 1u;       /* 1 */
unsigned ngat   =  cau_hinh       & 1u;       /* 0 */
terminal
./truong-bit
y             10110110  = 0xB6 = 182
MAT_NA        11110000  = 0xF0 = 240
y & MAT_NA    10110000  = 0xB0 = 176
roi >> 4      00001011  = 0x0B = 11

#Ba cạm bẫy còn lại

Một: dịch quá số bit của kiểu

uint8_t co = 0;

BAT_BIT(co, 10);          /* BIT(10) la 1024, khong nam trong uint8_t.
                             Phep OR duoc tinh tren int, roi cat con 8 bit,
                             nen co khong doi. Khong ai canh bao. */

/* Cach phong: kiem luc bien dich khi n la hang so */
#define BAT_BIT_KIEM(x, n)                                   \
    do {                                                     \
        _Static_assert((n) < sizeof(x) * CHAR_BIT,           \
                       "so hieu bit vuot do rong cua bien"); \
        (x) |= BIT(n);                                       \
    } while (0)

/* Chi chay khi n la hang so bien dich. Voi n la bien thi
   phai kiem luc chay: */
if (n < sizeof co * CHAR_BIT) BAT_BIT(co, n);

Hai: thăng hạng nguyên với kiểu nhỏ

uint8_t co = 0xFF;

uint8_t dao = ~co;        /* ~co tinh tren INT:
                             co thang hang thanh 255
                             ~255 = -256
                             gan vao uint8_t -> 0

                             Ket qua DUNG, nhung chi vi tinh co. */

uint16_t a = 0xFFFF;
uint32_t b = a << 16;     /* a thang hang thanh int co dau 32 bit
                             0xFFFF << 16 khong nam trong int
                             -> HANH VI KHONG XAC DINH */

uint32_t c = (uint32_t)a << 16;    /* dung */

/* Quy tac: voi uint8_t va uint16_t, EP sang unsigned hoac uint32_t
   truoc khi dich hoac dao. Bai 31.2 da noi, va no lap lai o day
   vi day la cho no hay xay ra nhat. */

Ba: quên rằng bit đánh số từ 0

/* Datasheet noi: "bit 5 la co bao san sang".

   Do la bit thu SAU tinh tu phai, va so hieu la 5.
   Mat na la BIT(5) = 0x20, khong phai BIT(6). */

uint32_t co_san_sang = KIEM_BIT(thanh_ghi, 5);

/* Voi mot truong tu bit 4 den bit 7:
     so bit  = 7 - 4 + 1 = BON bit
     mat na  = 0xF0
     gia tri = (x & 0xF0) >> 4

   Cong thuc so bit la hi - lo + 1, va dau cong 1 rat hay bi quen. */

Tự làm thử

  1. Viết bốn biểu thức bật, tắt, đảo, kiểm và chạy chúng trên 0xB1 với bit 2 và bit 4.
  2. Viết BIT(n) bằng 1 thay vì 1u rồi gọi BIT(31), chạy dưới UBSan nếu có.
  3. Viết ba bản MAT_NA ở trên và kiểm cả ba bằng năm trường hợp trong bảng.
  4. Ghi giá trị 0x3 vào trường bit 4 tới 7 của 0xB6 và xác nhận kết quả là 0x36.
  5. Ghi giá trị 0xFF vào cùng trường đó và xác nhận bit thừa bị che.
  6. Viết tệp kiểm thử đầy đủ cho bit.h của bạn.

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ốn thao tác: OR để bật, AND với mặt nạ đảo để tắt, XOR để đảo, dịch rồi AND với 1 để kiểm.
  • BIT(n) phải dùng 1u, và BIT64(n) phải dùng 1ULL cho bit từ 32 trở lên.
  • Ghi vào một trường nhiều bit cần đủ ba bước: xóa trường cũ, dịch giá trị mới lên vị trí, và che giá trị mới bằng mặt nạ.
  • Kiểm nhiều cờ phải phân biệt x & can nghĩa là bất kỳ với (x & can) == can nghĩa là tất cả.
  • Với uint8_t và uint16_t, ép sang uint32_t trước khi dịch, vì thăng hạng nguyên đưa chúng về int có dấu.